diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..8ccb6afe7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# WordPress Coding Standards +# http://make.wordpress.org/core/handbook/coding-standards/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = tab +indent_size = 4 + +[*.{json,yml,feature}] +indent_style = space +indent_size = 2 + +[composer.json] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..97cb2461f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Auto detect text files and perform EOL normalization +* text=auto eol=lf +tests/data/*-win.php eol=crlf diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7f557a8e2..6c6403151 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,17 +1,17 @@ # Community Guidelines -This guide details how to get involve in EasyEngine commmunity. Please read this carefully. +This guide details how to get involved in EasyEngine commmunity. Please read this carefully. ## How to get help? -Please attach the output of following command when open a new support request. +Please attach the output of following commands when opening a new support request. ```bash lsb_release -a -ee -v -ee info +ee cli version +ee cli info wp --allow-root --info ``` @@ -19,16 +19,16 @@ wp --allow-root --info For free support, please use - http://community.rtcamp.com/c/easyengine -Please do NOT clutter github issue tracker here with support requests. It hampers development speed of this project. +Please do NOT clutter GitHub issue tracker here with support requests. It hampers development speed of this project. ### Pull Requests -When submitting your code please follow this coding standerds - http://docs.rtcamp.com/easyengine/dev/python/ +When submitting your code, please follow these coding standards - http://docs.rtcamp.com/easyengine/dev/python/ ### EasyEngine Chat -Developer & contributor discussion: https://gitter.im/rtCamp/easyengine +Developers & contributors discussion: https://gitter.im/rtCamp/easyengine -Please do NOT use chat for technical support. Chat is limited to developer & contributor disucssion related to EasyEngine future. +Please do NOT use chat for technical support. Chat is limited to developers & contributors disucssion related to EasyEngine's future. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index b2e446cb8..5c7a16529 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,9 +1,9 @@ -This issue tracker is only for issues related to EasyEngine. Please use http://community.rtcamp.com/c/easyengine for support questions. +This issue tracker is only for issues related to EasyEngine. Please use https://github.com/EasyEngine/easyengine/discussions for support questions. -If you feel the issue is a EasyEngine core specific issue, please attach the output of the following commands. +If you feel the issue is a EasyEngine specific issue, please attach the output of the following commands. System Information +- [ ] ee cli info - [ ] lsb_release -a -- [ ] ee -v -- [ ] ee info -- [ ] wp --allow-root --info +- [ ] docker version +- [ ] docker-compose version diff --git a/.github/workflows/test_and_build.yml b/.github/workflows/test_and_build.yml new file mode 100644 index 000000000..3057c075d --- /dev/null +++ b/.github/workflows/test_and_build.yml @@ -0,0 +1,272 @@ +on: + pull_request: + push: + tags: + - "v*.*.*" + branches: + - master + - develop + schedule: + - cron: '30 3 * * *' + +name: Build 🔨 + Test 👨‍🔧 + +jobs: + build: #--------------------------------------------------------------------- + name: Build Phar + runs-on: ubuntu-latest + steps: + - name: Check out source code + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + tools: composer + extensions: pcntl, curl, sqlite3, zip, dom, mbstring, json + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Set up Composer caching + uses: actions/cache@v3 + env: + cache-name: cache-composer-dependencies + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install dependencies + run: | + cd "$GITHUB_WORKSPACE" + # Run composer install for master else update. + if [[ "$GITHUB_REF" = "refs/heads/master" ]] || [[ "$GITHUB_REF" = "refs/tags/"* ]]; then + composer install --no-dev --no-progress --no-interaction + else + sed -i 's/\(easyengine\/.*\):\ \".*\"/\1:\ \"dev-develop\"/' composer.json + composer update --prefer-dist --no-dev --no-progress --no-interaction + fi + + - name: Setup EE version + if: ${{ ! startsWith(github.ref, 'refs/tags/') }} + run: | + cd "$GITHUB_WORKSPACE" + if [[ "$GITHUB_REF" != $DEPLOY_BRANCH ]]; then + CLI_VERSION=$(head -n 1 VERSION) + CLI_VERSION="$(echo $CLI_VERSION | xargs)" + CLI_VERSION+="-nightly-$(git rev-parse --short HEAD)" + echo $CLI_VERSION > VERSION + echo "$CLI_VERSION" > VERSION + fi + env: + DEPLOY_BRANCH: "refs/heads/master" + + + - name: Upload EE version + uses: actions/upload-artifact@v4 + with: + name: cli_version + path: VERSION + + - name: Build the Phar file + run: php -dphar.readonly=0 utils/make-phar.php easyengine.phar + + - name: Check phar + run: sudo ./easyengine.phar cli info + + - name: Upload built Phar file + uses: actions/upload-artifact@v4 + with: + name: easyengine-phar + path: easyengine.phar + + test: #---------------------------------------------------------------------- + runs-on: ubuntu-latest + needs: [build] + name: Behat Tests - PHP ${{ matrix.php }} + strategy: + fail-fast: false + matrix: + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] + steps: + - name: Check out source code + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '${{ matrix.php }}' + coverage: none + tools: composer + extensions: pcntl, curl, sqlite3, zip, dom, mbstring, json + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Set up Composer caching + uses: actions/cache@v3 + env: + cache-name: cache-composer-dependencies + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Update docker + run: | + sudo apt purge nginx nginx-common docker docker-engine docker.io docker-ce containerd runc + curl -fsSL https://get.docker.com/ | sudo bash + sudo systemctl restart docker.service + + - name: Install docker-compose + run: | + sudo curl -L https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + + - name: Install dependencies + run: | + cd "$GITHUB_WORKSPACE" + # Run composer install for master else update. + if [[ "$GITHUB_REF" = "refs/heads/master" ]]; then + composer install --prefer-dist --no-progress --no-interaction + else + sed -i 's/\(easyengine\/.*\):\ \".*\"/\1:\ \"dev-develop\"/' composer.json + composer update --prefer-dist --no-progress --no-interaction + fi + + - name: Test + shell: 'script -q -e -c "bash {0}"' + run: | + set -e + cd $GITHUB_WORKSPACE + sudo -E ./vendor/bin/behat + sub_commands=( + vendor/easyengine/site-command/features + vendor/easyengine/site-type-php/features + vendor/easyengine/site-type-wp/features + ) + for command in "${sub_commands[@]}"; do + IFS='/' read -r -a array <<< "$command" + rm -rf features/* + rsync -av --delete $command/ features/ > /dev/null + for file in features/*.feature; do mv "$file" "${file%.feature}_${array[2]}.feature"; done + echo "Running tests for $command" + sudo -E ./vendor/bin/behat + done + env: + COMPOSE_INTERACTIVE_NO_CLI: 1 + + - name: Output logs + if: ${{ always() }} + run: | + [[ -f "/opt/easyengine/logs/install.log" ]] && cat /opt/easyengine/logs/install.log || echo 'No install log.' + [[ -f "/opt/easyengine/logs/ee.log" ]] && cat /opt/easyengine/logs/ee.log || echo 'No run log.' + + deploy: #----------------------------------------------------------------------- + name: Deploy Phar + if: | + github.repository_owner == 'EasyEngine' && ( + startsWith(github.ref, 'refs/tags/') || + github.ref == 'refs/heads/develop' + ) + runs-on: ubuntu-latest + needs: [build, test] + + steps: + - name: Check out builds repository + uses: actions/checkout@v3 + with: + repository: easyengine/easyengine-builds + token: ${{ secrets.BOT_TOKEN }} + + - name: Download built Phar file + uses: actions/download-artifact@v4 + with: + name: easyengine-phar + + - name: Set file name + if: ${{ contains(github.ref, 'develop') }} + run: | + echo 'FILENAME=easyengine-nightly.phar' > $GITHUB_ENV + - name: Set file name for tag + if: ${{ contains(github.ref, 'refs/tags') }} + run: | + echo 'FILENAME=easyengine.phar' > $GITHUB_ENV + + - name: Move built Phar file into correct location + run: | + mv easyengine.phar phar/$FILENAME + + - name: Make built Phar executable + run: | + chmod +x phar/$FILENAME + + - name : Create hashes + run: | + md5sum phar/$FILENAME | cut -d ' ' -f 1 > phar/$FILENAME.md5 + sha512sum phar/$FILENAME | cut -d ' ' -f 1 > phar/$FILENAME.sha512 + + - name: Commit files + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add phar/$FILENAME phar/$FILENAME.md5 phar/$FILENAME.sha512 + git commit -m "phar build: $GITHUB_REPOSITORY@$GITHUB_SHA" + + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.BOT_TOKEN }} + branch: master + repository: easyengine/easyengine-builds + + release: #----------------------------------------------------------------------- + name: Create release + if: | + github.repository_owner == 'EasyEngine' && + startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + needs: [build, test] + + steps: + - name: Check out source code + uses: actions/checkout@v3 + + - name: Download built Phar file + uses: actions/download-artifact@v4 + with: + name: easyengine-phar + + - name: Create hashes + run: | + md5sum easyengine.phar | cut -d ' ' -f 1 > easyengine.phar.md5 + sha512sum easyengine.phar | cut -d ' ' -f 1 > easyengine.phar.sha512 + + - name: Authenticate gh-cli + run: echo ${{ secrets.BOT_TOKEN }} | gh auth login --with-token + + - name: Generate changelog + run: bash utils/changelog.sh + + - name: Get tag + id: tag + run: echo tag=${GITHUB_REF/refs\/tags\//} >> $GITHUB_OUTPUT + + - name: Release + uses: softprops/action-gh-release@v1 + with: + body_path: changelog.txt + files: | + easyengine.phar + easyengine.phar.md5 + easyengine.phar.sha512 + draft: true + name: EasyEngine ${{ steps.tag.outputs.tag }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 41b775bf7..269eb2159 100644 --- a/.gitignore +++ b/.gitignore @@ -1,66 +1,23 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -.idea/ - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.cache -nosetests.xml -coverage.xml - -# Translations -*.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - - -# Vim .swp file -*.swp - -# Folder created for Nose testing -bin/ -coverage_report/ -include/ -local/ -man/ +.DS_Store +config.yml +PHAR_BUILD_VERSION +/cache +/packages +/vendor +/*.phar +/phpunit.xml.dist +/codesniffer +/PHP_Codesniffer-VariableAnalysis +.*.swp +launch.json +ee.iml +modules.xml +php.xml +php-test-framework.xml +vcs.xml +workspace.xml +profiles_settings.xml +Project_Default.xml +.idea +ee_release +changelog.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b487e3ff5..000000000 --- a/.travis.yml +++ /dev/null @@ -1,114 +0,0 @@ -notifications: - slack: easyengine:76AI30tP8P8AcNTaWaQ9ZAT7 - webhooks: - urls: - - https://webhooks.gitter.im/e/bd77a26eab56de803949 - - https://webhooks.gitter.im/e/e3e2feb8384c77bf1a8a - on_success: always # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: false # default: false - -language: bash - -before_install: - - rm -rf ~/.gnupg - -before_script: - - sudo rm -rf /etc/mysql/ - - sudo bash -c 'echo example.com > /etc/hostname' - - sudo service hostname restart - - sudo apt-get -qq purge mysql* graphviz* - - sudo apt-get -qq autoremove - - sudo apt-get update - -script: - - lsb_release -a - - unset LANG - - sudo bash -c 'echo -e "[user]\n\tname = abc\n\temail = root@localhost.com" > /home/travis/.gitconfig' - - sudo echo "Travis Banch = $TRAVIS_BRANCH" - - sudo apt-get install -y --force-yes git python3-setuptools python3-dev python3-apt - - sudo bash install $TRAVIS_BRANCH - - sudo ee --help - - sudo ee stack install || sudo tail -n50 /var/log/ee/ee.log - - sudo ee stack install --web || sudo tail -n50 /var/log/ee/ee.log - - sudo ee stack install --admin || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site create html.net --html || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create php.com --php || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create mysql.com --mysql || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site1.com --wp || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site create site2.net --wp --wpsc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site3.net --wp --w3tc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site4.com --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site4.net --wp --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site4.org --wpfc --wp || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site5.com --wpsubdir || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site create site6.com --wpsubdir --wpsc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site7.com --wpsubdir --w3tc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site8.com --wpsubdir --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site8.net --wpfc --wpsubdir || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site9.com --wpsubdomain || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site create site10.org --wpsubdomain --wpsc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site11.org --wpsubdomain --w3tc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site12.org --wpsubdomain --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site12.in --wpfc --wpsubdomain || sudo tail -n50 /var/log/ee/ee.log - - - yes | sudo ee site create site.hhvm.pagespeed2.com --wpsc --hhvm || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site.hhvm.pagespeed4.com --wpfc --hhvm || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site.hhvm.pagespeed5.com --wpsubdir --hhvm || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site.hhvm.pagespeed6.com --wpsubdir --wpsc --hhvm || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site.hhvm.pagespeed8.com --wpsubdir --wpfc --hhvm || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site.hhvm.pagespeed9.com --wpsubdomain --hhvm || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site.hhvm.pagespeed10.org --wpsubdomain --wpsc --hhvm || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site.hhvm.pagespeed12.in --wpfc --wpsubdomain --hhvm || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site create site1.localtest.me --php --mysql || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site2.localtest.me --mysql --html || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site3.localtest.me --php --html || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site4.localtest.me --wp --wpsubdomain || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create site5.localtest.me --wp --wpsubdir --wpfc || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site6.localtest.me --wpredis || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site7.localtest.me --wpsubdomain --wpredis || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site create site8.localtest.me --wpsubdir --wpredis || sudo tail -n50 /var/log/ee/ee.log - - - - sudo ee debug --all || sudo tail -n50 /var/log/ee/ee.log - - sudo ee debug --all=off || sudo tail -n50 /var/log/ee/ee.log - - sudo ee debug site12.net || sudo tail -n50 /var/log/ee/ee.log - - sudo ee debug site12.net --all=off || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create 1.com --html || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create 2.com --php || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create 3.com --mysql || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site update 1.com --wp || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site update 2.com --wpsubdir || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site update 3.com --wpsubdomain || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site update site1.com --wp --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site update site1.com --wp --w3tc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site update site1.com --wp --wpsc || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site update site1.com --wpredis || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site update site5.com --wpsubdir --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site update site5.com --wpsubdir --wpsc || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site update site9.com --wpsubdomain --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site update site9.com --wpsubdomain --wpsc || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site update site.hhvm.pagespeed12.in --hhvm=off || sudo tail -n50 /var/log/ee/ee.log - - yes | sudo ee site update site9.com --hhvm || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site info site.hhvm.pagespeed12.in || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site info site9.com || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site create www.site-1.com --wp || sudo tail -n50 /var/log/ee/ee.log|| sudo tail -n50 /var/log/ee/ee.log - - sudo ee site create www.subsite.site-1.com --wpfc || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site update www.subsite.site-1.com --wp || sudo tail -n50 /var/log/ee/ee.log - - sudo ee site delete www.subsite.site-1.com --all --no-prompt || sudo tail -n50 /var/log/ee/ee.log - - - sudo ee site delete site12.in --all --no-prompt || sudo tail -n50 /var/log/ee/ee.log - - - sudo ls /var/www/ - - sudo wp --allow-root --info - - sudo bash -c 'cat /var/log/ee/ee.log' diff --git a/CHANGELOG.txt b/CHANGELOG.txt deleted file mode 100644 index 81e34d77a..000000000 --- a/CHANGELOG.txt +++ /dev/null @@ -1,470 +0,0 @@ -v3.8.1 - May 30, 2018 -- Fix update command -- Fix issue with admin tools urls - -v3.8.0 - May 29, 2018 -- Add support for Ubuntu 18.04 and remove support for Ubuntu 12.04 -- Nginx build for EasyEngine updated to 1.14.0 -- Update WP-CLI version to 1.5.1 -- Update Roundcube to 1.3.6 -- Update Adminer to 4.6.2 - -v3.7.5 - Mar 30, 2018 -- Replace pymysql3 with PyMySQL [critical] -- Upgrade wp-cli version to 1.4.1 -- Update templates to skip cache for more plugins - -v3.7.4 - Aug 29, 2016 -- Fix phpmemcacheadmin download urls [critical] - -v3.7.3 - Aug 17, 2016 -- Fix wp-cli compatibility with WordPress 4.6 [critical] - -v3.7.2 - Jul 20, 2016 -- CGI Vulnerability HTTPOXY Patch - -v3.7.1 - Jul 1, 2016 -- Fix PHP7 support for debian jessie -- Fix PHP7 debug log - -v3.7.0 - Jun 22, 2016 -- PHP7 support for debian jessie -- Fixed Issue #729, #724 - -v3.6.2 - Jun 03, 2016 -- Fix Issue #726,#723,#727 -- Update Adminer to v4.2.5 -- Check for already installed nginx package - -v3.6.1 - May 20, 2016 -- Fix HHVM Support on Ubuntu Xenial - -v3.6.0 - May 16, 2016 -- Add support for Ubuntu 16.04 LTS - Xenial. -- Updated Nginx to the latest stable version (1.10.1) -- Updated OpenSSL to v1.0.2g for HTTP/2 support -- Drop support for spdy module. Move existing spdy sites to http2. -- Drop support for ngx_pagespeed module. -- Drop support for Nginx Mainline builds. -- Fix Let's Encrypt Auto renew issue - -v3.5.6 - May 5, 2016 -- Patch for ImageMagick vulnerability CVE-2016–3714 - https://imagetragick.com/ -- Minor Fix for letsencrypt - -v3.5.5 - Apr 13, 2016 -- WP-CLI support for wordpress v4.5, PR #701 -- PR #686 -- Fix #702 -- Minor Fixes - -v3.5.4 - Mar 15, 2016 -- Fixed Vimbadmin installation issue -- PR #684 -- Minor fixes - -v3.5.3 - Feb 25, 2016 -- Fixed #650 -- Fixed #681 - -v3.5.2 - Feb 25, 2016 -- Fixed PHP package dependency (Trusty) - -v3.5.1 - Feb 25, 2016 -- Fixed #680 - -v3.5.0 - Feb 24, 2016 -- Added PHP 7.0 support for Ubuntu(Trusty) -- Dual support for PHP 5.6 + PHP 7.0 -- Fixed minor issues - -v3.4.1 - Jan 20, 2016 -- Fixed and update ee stack install --nginx/--nginxmainline -- Fixed --letsencrypt minor issues -- Fixed #665 #659 -- Fixed other minor bug - -v3.4.0 - Jan 6, 2016 -- Added Let's Encrypt support - - ee site create example.com [--wp/..] --letsencrypt - - ee site update example.com --letsencrypt=on/off/renew - - Automatic renew of certs through cron - - Mail notification added on certs renew - - Alias command: --le -- Added HTTP/2 support - - ee stack install/remove/purge --nginxmainline -- Check SSL cert status/expiry date in site info - - ee site info example.com -- Update autocompletion and man page - -v3.3.15 - Dec 9, 2015 -- Upgrade wp-cli version to 0.21.1 - -v 3.3.14 - Nov 10, 2015 -- Fix EasyEngine installation issue - -v 3.3.13 - Nov 10, 2015 -- Security Fix in redis.conf -- Update wp-cli version - -v 3.3.12 - Nov 3, 2015 -- Fix #637 -- Updated wp-cli version - -v 3.3.11 - Oct 26, 2015 -- Updated MySql version -- Added new repository for MySql -- Minor fix for redis cache in debian jessie -- Security Fix in location.conf - -v 3.3.10 - Oct 19, 2015 -- Fix #630 -- Fix #631 -- Updated wp-cli version - -v 3.3.9 - Oct 9, 2015 -- Fix #625 -- Fix #627 -- Fix phpmyadmin for remote mysql server - -v 3.3.8 - Sep 9, 2015 -- # Fix White screen for NGINX PLUS users - -v 3.3.7 - Sep 9, 2015 -- EasyEngine + NGINX PLUS (http://docs.rtcamp.com/easyengine/nginx-plus/) - -v 3.3.6 - Sep 4, 2015 -- Fix #611 - -v 3.3.5 - Sep 2, 2015 -- Introduced new support of --force/-f in delete command of site - - Removes database entry or nginx configuration of site from system - - ee site delete example.com --force/-f - - ee site delete example.com --force/-f --db/--files -- Moved ~/.my.cnf to /etc/mysql/conf.d/my.cnf - - Fixed MySQL credential issue -- Fixed Bug in cache clean --all command -- Improved 404 handling for static HTML sites #561 -- Improved cleanup action while site create command fail -- Updated wpfc config #543 -- Improved ee sync command https://github.com/rtCamp/easyengine/issues/575#issuecomment-126293815 -- Bug Fix #486 -- Bug Fix #555 -- Updated man page -- Fixed bug in ee update site to --wpredis -- Fixed Autocompletion Issue #519 -- Fixed minor bug in update --password -- Minor fix and improvements - -v 3.3.4 - Aug 12, 2015 -- Bug Fix for https://github.com/rtCamp/easyengine/issues/598#issue-98354466 - -v 3.3.3 - Jul 28, 2015 -- Bug Fix for https://github.com/rtCamp/easyengine/issues/593#issuecomment-125274633 - -v 3.3.2 - Jul 27, 2015 -- Bug Fix #593 -- Fix package authentication problems -- Version update roundcube 1.1.2 -- Version update vimbadmin to 3.0.12 - -v 3.3.1 - Jul 23, 2015 -- Fixed redis query expiration timeout limit -- Updated redis cache header to more informative - -v 3.3.0 - Jul 13, 2015 -- Added support for Redis full page cache -- Added support to update WPCLI -- Added support for custom WPCLI location -- Updated WPCLI to v0.19.2 -- Refined ee site update command -- autoconfigure nginx-helper plugin - -v 3.2.2 - Jun 19, 2015 -- Fixed bug that was preventing disabling debug - -v 3.2.1 - Jun 19, 2015 -- Improved experimental HHVM and Pagespeed handling, fixes #564, #562 -- Fixed missing link for Proxy sites #571 -- Added hhvm.server.ip for HHVM config #567 - -v 3.2.0 - May 28, 2015 -- Now using openSuse builder to build Nginx package for both Ubuntu and Debian -- Minor fixes and improvements - -v 3.1.9 - May 23, 2015 -- Fixed "ssl_prefer_server_ciphers" directive is duplicate after ee stack upgrade on Debian #552 - -v 3.1.8 - May 22, 2015 -- Fixed PHP Failed to Restart after Upgrade #550 -- Improved ee stack services command -- Minor fix and improvements - -v 3.1.7 - May 21, 2015 -- Fixed site blank issue after nginx stack upgrade on Debian -- Improved update script - -v 3.1.6 - May 19, 2015 -- Added Debian 8 support, see #524 -- EasyEngine now uses dphys-swapfile for swap creation, see #359 -- Minor bug fixes and improvements. - -v 3.1.5 - May 14, 2015 -- Fixed Debain 7 stack issues. see #538 #539 #540 #435 #546 -- Updated MySQLTuner see #484 -- Minor fixes and improvements. - -v 3.1.4 - May 7, 2015 -- Fixed XSS Vulnerability found in some WordPress themes and plugins - -v 3.1.3 - May 6, 2015 -- EasyEngine now fixes missing GPG keys automatically, fixes #470 -- Fixed Nginx hash bucket issue, fixes #449 - -v 3.1.2 - April 29, 2015 -- Updated PHP version from PHP 5.5 to PHP 5.6 #425 -- Added ee stack upgrade command for stack upgrade #458 -- Added support for Proxy site creation #389 -- Added extra parameters such as User, Password and EMail during site creation #453 -- Fixed timezone related warnings and errors #451 - - -v 3.1.1 - April 21, 2015 -- Feature: update all site at once #491 -- Tweaked import-slow-log command #322 -- Fixed HHVM installation without Nginx #492 -- Fixed HHVM not running after reboot #498 -- Fixed Nginx config on Debian causing failure in site creation #499 -- Now support for existing HHVM on server -- Minor fixes and improvements - -v 3.1.0 - April 16, 2015 -- Added HHVM support #199 -- Added Pagespeed support #473 -- EasyEngine log command now suppots more subcommand #448 -- Before stack purge or remove, EasyEngine now asks confirmation #485 -- Minor fixes and improvements - -v 3.0.10 - April 10, 2015 -- Fixed regression issue introduced in 3.0.9 - -v 3.0.9 - April 10, 2015 -- Fixed #483, now Xdebug is enabled during ee debug only and disabled by default -- Introduced ee sync command to sync EasyEngine database with Site information -- Minor fixes and improvement - -v 3.0.8 - April 9,2015 -- Fixed regression issue introduced in 3.0.7 - -v 3.0.7 - April 9,2015 -- Fixed #445 -- Fixed #413 -- Small bug fixes. -- Improvements in core functionality. - -v 3.0.6 - April 2,2015 -- Added `update` command to update EasyEngine. - -v 3.0.5 - March 18,2015 -- Changed Database from Percona MySQL to MariaDB #362 -- Better arguments for ee debug command -- Improved installation script -- Small bug fixes and improvements - -v 3.0.4 - Feb 25,2015 -- Fixed #406, install Wp Query Monitor on wp debug option -- Fixed #429, added `log` command -- Fixed #442 -- Fixed bug in delete command for site -- Small bug fixes and improvements - -v 3.0.3 - Feb 23,2015 -- Fixed remote MySQL supports, #428, #427, thanks to @joshlyford #432 -- Fixed ascii encoding issue #414 -- Fixed bug in debug pool #426 -- Added admin tools to default stack -- Added more MIME types,thanks to @cborgia #423 -- Small bugs fixes and improvements - -v 3.0.2 - Feb 14,2015 -- Fixed #413 -- Improved installation script -- Fixed Percona key not importing -- Small bug fixes and improvements - -v 3.0.1 - Feb 12,2015 -- Fixed #411, #40, #409 -- Improved installation script - -v 3.0.0 - Feb 11,2015 -- Refactored complete code in Python 3 -- Updated WP-CLI to 0.18 -- Update RoundCube to 1.1 -- Update ViMbAdmin to 3.0.11 - -v 2.2.3 - Feb 01,2015 -- Fixed installation issue of Percona Mysql - -v 2.2.2 - Dec 03,2014 -- Fixed installation issue of Percona Mysql on Debian -- Fixed error during ViMbAdmin installation -- Fixed alias in ViMbAdmin are not working -- Updated WP-CLI to 0.17.1 - -v 2.2.1 - Oct 16,2014 -- Fixed update script issue for remote MySQL #323 -- Fixed remote MySQL delete user issue by @brennentsmith #324 -- Improved auto-completion - -v 2.2.0 - Oct 13, 2014 -- Percona Mysql 5.6 support on Fresh installation. #107 -- RAM Based Optimization for PHP5-FPM,OpCache and Memcache -- Introduced following new commands for - - ee site cd example.com # Change Directory to example.com Webroot - - ee site log example.com # Real time log monitoring for example.com access and error logs - - ee site update example.com # update example.com cache and convert site - - ee clean # Clean cache NGINX FastCGI, Memcache, OPcache. -- Change WordPress user password easily ( ee site update example.com --password) #315 -- Added PHP5-GeoIP Support -- Auto Swap creation for low RAM servers. -- Fixed Better autocompletion feature. Fixed #311. -- Fixed phpMyAdmin (blowfish_secret) error #301 -- Simplified way to update EasyEngine. #307 - -v 2.1.0 - Sept 3, 2014 - - Added Mail Server Package installation #65 - - Fixed incorrect log file path during debug #299 - - Added support for pt-query-advisor #189 - - Fixed Firefox cross-domain fonts - - Fixed ee site edit command on Nano editor - -v 2.0.2 - Aug 13, 2014 - - Remote MySQL Support - -v 2.0.1 - July 21, 2014 - - Fixed wp-cli installation #289 - -v 2.0.0 - July 14, 2014 - - Completly rewritten code - - Updated wp-cli to 0.16.0 - - Introduced ee secure command - - Changed the way to update EasyEngine #179 - - Fixed security on 22222 port by @RenzoF #263 - - Fixed MySQL stopped in ee stack status #233 - - Added WordPress SEO Yoast plugin support in wpcommon.conf #165 - - Depricated ee system command with ee stack command - - -v 1.3.8 - May 26, 2014 - - Fixed phpMyAdmin download issue #251 - - Fixed swap issue #223 by @Swingline0 - - Delete website without prompt by @edwinvandeven #239 - - -v 1.3.7 - Apr 29, 2014 - - Fixed EasyEngine Update Problem - - Fixed Issue #206 - - Update nginx version 1.6.0 - - Update wp-cli version 0.14.1 - - -v 1.3.6 - Apr 24, 2014 - - Fixed Nginx Update Problem #201 - - Automate Testing Using Travis-ci - - -v 1.3.5 - Apr 22, 2014 - - Update nginx to the latest stable version (1.4.7) - - -v 1.3.4 - Apr 22, 2014 - - Supports Ubuntu 12.04, 12.10, 13.10 & 14.04 ( Fixed Issue #94 #195 ) - - Change FPM process management from dynamic to ondemand #184 - - Fixed Issue #188 - - Fixed Debian 6 wget command issue - - -v 1.3.3 - Apr 16, 2014 - - Fixed issue #186 - - Fixed issue #187 - - -v 1.3.2 - Apr 14, 2014 - - Fixed ee update - - -v 1.3.1 - Apr 14, 2014 - - Fixed Autocompletion Issue #185 - - ee info command display php5-fpm process manager details (dynamic/ondemand) - - -v 1.3.0 - Apr 11, 2014 - - Introduce `ee debug` command - - Introduce `ee system [status|stop|start|restart]` command - - - EasyEngine Admin Tools shifted on port `22222` with self signed SSL Certificate #124 - - Setup Separate PHP5-FPM Pool on Port 9001 for debugging purpose - - Polish `ee site edit` command #157 - - Fixed MySQL Username Character Limit #113 - - Nginx Settings #100 - - -v 1.2.2 - Mar 18, 2014 - - Check/Install bc command - - Fixed EasyEngine Update Issue #134 #148 - - Automatic set WordPress Permalink Structure by @UmeshSingla - - Fixed @WP-CLI version to 0.14.1 - - Correct Typo Mistake, Thanks to @sdenike on pull request #155 - - Introduce ee site edit command by @Mermouy on pull request #153 - - Auto Switch the site create options - - Better way to detect Linux Ditribution - - -v 1.2.1 - Mar 3, 2014 - - Fixed Debian Issue #136 - - -v 1.2 - Feb 20, 2014 - - Debian 6 and Debian 7 support added - - -v 1.1.5 - Feb 17, 2014 - - Fixed WordPress multisite issue (#127). - - -v 1.1.4 - Feb 14, 2014 - - Fixed wp-cli issue with --allow-root error. - - -v 1.1.3 - Jan 28, 2014 - - Added GPL License. Ref - https://rtcamp.com/easyengine-is-gpl/ - - -v 1.1.2 - Jan 28, 2014 - - Fix White-list IP Address - - Fix ee info command - - Fix site delete issue (#111) - - -v 1.1.1 - Jan 17, 2014 - - IP-detcect issue. (#108) - - -v 1.1 - Jan 16, 2014 - - Handling redirects from “non-www to www” and vice-versa. (#71) - - Automating mysql setup. Every site which needs a database gets a new mysql user and password automatically. mysql root is not used anymore for new sites. (#72) - - Automated postfix installation. (#72) - - Added “ee info” command (#69) - - Secured /ee locations (#60 and https://rtcamp.com/tutorials/nginx/ssl-pci-compliance-performance/) - - SSL Session cache for nginx (#56) - - -v 1.0.1 - Dec 17, 2013 - - Fix wp-cli issue - - -v 1.0.0 - Oct 1, 2013 - - First Release - - Support for Standard WordPress and multisite WordPress - - Support for Nginx Fastcgi-Cache, W3 Total Cache and WP Super Cache - - Support for PHP and HTML sites diff --git a/LICENSE b/LICENSE index 054750809..46848945e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 rtCamp Solutions Private Limited +Copyright (C) 2011-2017 EE Development Group (https://github.com/ee/ee/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2282fb255..8bdf7c249 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,115 @@ -# [EasyEngine](https://easyengine.io/) -[![Travis Build Status](https://travis-ci.org/EasyEngine/easyengine.svg)](https://travis-ci.org/EasyEngine/easyengine) [![Join EasyEngine Slack Channel](http://slack.easyengine.io/badge.svg)](http://slack.easyengine.io/) +EasyEngine Logo -**Update:** [We are working on next major release (v4) which will be in PHP and based on WP-CLI](https://easyengine.io/blog/easyengine-v4-development-begins/). +# EasyEngine v4 +[![Build 🔨 + Test 👨‍🔧](https://github.com/EasyEngine/easyengine/actions/workflows/test_and_build.yml/badge.svg?branch=develop)](https://github.com/EasyEngine/easyengine/actions/workflows/test_and_build.yml) +[![Latest Stable Version](https://poser.pugx.org/easyengine/easyengine/v/stable)](https://github.com/EasyEngine/easyengine/releases) [![License](https://poser.pugx.org/easyengine/easyengine/license)](https://packagist.org/packages/easyengine/easyengine) -EasyEngine Logo +EasyEngine makes it greatly easy to manage nginx, a fast web-server software that consumes little memory when handling increasing volumes of concurrent users. -EasyEngine (ee) is a python tool, which makes it easy to manage your wordpress sites running on nginx web-server. +## Requirements -**EasyEngine currently supports:** +* Docker +* Docker-Compose +* PHP CLI (>=7.1) +* PHP Modules - `curl`, `sqlite3`, `pcntl` -- Ubuntu 12.04 & 14.04 & 16.04 -- Debian 7 & 8 +## Installing -**Port Requirements:** +### Linux -| Name | Port Number | Inbound | Outbound | -|:-----:|:-----------:|:-------:|:---------:| -|SSH |22 | ✓ |✓ | -|HTTP |80 | ✓ |✓ | -|HTTPS/SSL |443 | ✓ |✓ | -|EE Admin |22222 | ✓ | | -|GPG Key Server |11371 | |✓ | - -## Quick Start +For Linux, we have created an installer script that will install all the dependencies for you. We have tested this on Ubuntu 14.04, 16.04, 18.04, 20.04, 22.04 and Debian 8, Debian 10. ```bash -wget -qO ee rt.cx/ee && sudo bash ee # Install easyengine 3 -sudo ee site create example.com --wp # Install required packages & setup WordPress on example.com +wget -qO ee https://rt.cx/ee4 && sudo bash ee ``` -## Update EasyEngine +Even if the script doesn't work for your distribution, you can manually install the dependencies and then run the following commands to install EasyEngine +```bash +wget -O /usr/local/bin/ee https://raw.githubusercontent.com/EasyEngine/easyengine-builds/master/phar/easyengine.phar +chmod +x /usr/local/bin/ee +``` -Update procedure for EasyEngine to latest version +### Tab completions -#### For current installed version prior to 3.0.6 -```bash -wget -qO ee rt.cx/ee && sudo bash ee +EasyEngine also comes with a tab completion script for Bash and ZSH. Just download [ee-completion.bash](https://raw.githubusercontent.com/EasyEngine/easyengine/develop-v4/utils/ee-completion.bash) and source it from `~/.bash_profile`: -``` -#### If current version is after than 3.0.6 -``` -ee update +```bash +source /FULL/PATH/TO/ee-completion.bash ``` -## More Site Creation Commands +Don't forget to run `source ~/.bash_profile` afterwards. -### Standard WordPress Sites +If using zsh for your shell, you may need to load and start `bashcompinit` before sourcing. Put the following in your `.zshrc`: ```bash -ee site create example.com --wp # install wordpress without any page caching -ee site create example.com --w3tc # install wordpress with w3-total-cache plugin -ee site create example.com --wpsc # install wordpress with wp-super-cache plugin -ee site create example.com --wpfc # install wordpress + nginx fastcgi_cache -ee site create example.com --wpredis # install wordpress + nginx redis_cache +autoload bashcompinit +bashcompinit +source /FULL/PATH/TO/ee-completion.bash ``` -### WordPress Multsite with subdirectory +## Usage -```bash -ee site create example.com --wpsubdir # install wpmu-subdirectory without any page caching -ee site create example.com --wpsubdir --w3tc # install wpmu-subdirectory with w3-total-cache plugin -ee site create example.com --wpsubdir --wpsc # install wpmu-subdirectory with wp-super-cache plugin -ee site create example.com --wpsubdir --wpfc # install wpmu-subdirectory + nginx fastcgi_cache -ee site create example.com --wpsubdir --wpredis # install wpmu-subdirectory + nginx redis_cache +To get started with EasyEngine and create a wordpress site, run + +``` +ee site create example.com --type=wp ``` -### WordPress Multsite with subdomain +Need a wordpress site with caching? Try -```bash -ee site create example.com --wpsubdomain # install wpmu-subdomain without any page caching -ee site create example.com --wpsubdomain --w3tc # install wpmu-subdomain with w3-total-cache plugin -ee site create example.com --wpsubdomain --wpsc # install wpmu-subdomain with wp-super-cache plugin -ee site create example.com --wpsubdomain --wpfc # install wpmu-subdomain + nginx fastcgi_cache -ee site create example.com --wpsubdomain --wpredis # install wpmu-subdomain + nginx redis_cache +``` +ee site create example.com --type=wp --cache ``` -### Non-WordPress Sites -```bash -ee site create example.com --html # create example.com for static/html sites -ee site create example.com --php # create example.com with php support -ee site create example.com --mysql # create example.com with php & mysql support +Need a wordpress multi-site with page cache? +``` +ee site create example.com --type=wp --mu=subdir --cache ``` -### HHVM Enabled Sites -```bash -ee site create example.com --wp --hhvm # create example.com WordPress site with HHVM support -ee site create example.com --php --hhvm # create example.com php site with HHVM support +Need a plain and simple html site? +``` +ee site create example.com ``` -### PageSpeed Enabled Sites -```bash -ee site create example.com --wp --pagespeed # create example.com WordPress site with PageSpeed support -ee site create example.com --php --pagespeed # create example.com php site with PageSpeed support +Want to play around with your new site? +``` +ee shell example.com ``` -## Cheatsheet - Site creation +Want to know more? Checkout readme of these commands - + * [site command](https://github.com/EasyEngine/site-command/) + * [site-wp command](https://github.com/EasyEngine/site-wp-command/) + * [cron command](https://github.com/EasyEngine/cron-command/) + * [shell command](https://github.com/EasyEngine/shell-command/) + +Note: :warning: EasyEngine will currently only run with root privileges. You can run `ee help`, `ee help site` and `ee help site create --type=wp` to get all the details about the various commands and subcommands that you can run. +## Development -| | Single Site | Multisite w/ Subdir | Multisite w/ Subdom | -|--------------------|---------------|-----------------------|--------------------------| -| **NO Cache** | --wp | --wpsubdir | --wpsubdomain | -| **WP Super Cache** | --wpsc | --wpsubdir --wpsc | --wpsubdomain --wpsc | -| **W3 Total Cache** | --w3tc | --wpsubdir --w3tc | --wpsubdomain --w3tc | -| **Nginx cache** | --wpfc | --wpsubdir --wpfc | --wpsubdomain --wpfc | -| **Redis cache** | --wpredis | --wpsubdir --wpredis | --wpsubdomain --wpredis | +Development of easyengine is done entirely on GitHub. -## Useful Links -- [Documentation](https://easyengine.io/docs/) -- [FAQ](https://easyengine.io/faq/) -- [Conventions used](https://easyengine.io/wordpress-nginx/tutorials/conventions/) +We've used [wp-cli](https://github.com/wp-cli/wp-cli/) framework as a base and built EasyEngine on top of it. -## Community Guides -- [Develop and Deploy with EasyEngine + VVV + Wordmove](https://github.com/joeguilmette/ee-vvv-wordmove) +This repo contains the main core of easyengine (the framework). +All top level commands(except `ee cli`) i.e. `ee site`, `ee shell` have their own repos. + +Currently, we have the following commands which are bundled by default in EasyEngine: + +* [site command](https://github.com/EasyEngine/site-command/) +* [shell command](https://github.com/EasyEngine/shell-command/) + +In future, the community will be able to make their own packages and commands! + +## Contributing + +We warmheartedly welcome all contributions however and in whatever capacity you can either through Pull Requests or by reporting Issues. You can contribute here or in any of the above mentioned commands repo. ## Donations [![PayPal-Donate](https://cloud.githubusercontent.com/assets/4115/5297691/c7b50292-7bd7-11e4-987b-2dc21069e756.png)](http://rt.cx/eedonate) -[![BitCoin-Donate](https://bitpay.com/img/donate-button.svg)](https://bitpay.com/417008/donate) -## License -[MIT](http://opensource.org/licenses/MIT) +## Does this interest you? + +Handcrafted Enterprise WordPress Solutions by rtCamp diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..ce609565d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +## Security + +EasyEngine takes the security of our software products seriously, which includes all source code repositories managed through our GitHub organizations. + +If you believe you have found a security vulnerability in any EasyEngine repository, please report it to us at sys+gh@rtcamp.com. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in English. diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..ad96464c4 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +4.10.1 diff --git a/bin/README.md b/bin/README.md new file mode 100644 index 000000000..5ef232097 --- /dev/null +++ b/bin/README.md @@ -0,0 +1,4 @@ +bin +=== + +Entrypoint to the php scripts which also sets the required enviournment variables. \ No newline at end of file diff --git a/bin/ee b/bin/ee new file mode 100755 index 000000000..9d2d05aac --- /dev/null +++ b/bin/ee @@ -0,0 +1,45 @@ +#!/usr/bin/env sh +# +# This wrapper script has been adapted from the equivalent drush wrapper +# and 99.9% of all credit should go to the authors of that project: +# http://drupal.org/project/drush +# And 0.09% to the author of this project: +# https://github.com/88mph/wpadmin/blob/master/wpadmin.php + +# Get the absolute path of this executable +ORIGDIR="$(pwd)" +SELF_PATH="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" && SELF_PATH="$SELF_PATH/$(basename -- "$0")" + +# Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink. +while [ -h "$SELF_PATH" ]; do + # 1) cd to directory of the symlink + # 2) cd to the directory of where the symlink points + # 3) Get the pwd + # 4) Append the basename + DIR="$(dirname -- "$SELF_PATH")" + SYM="$(readlink "$SELF_PATH")" + SELF_PATH="$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")" +done +cd "$ORIGDIR" + +if [ ! -z "$EE_PHP" ] ; then + # Use the EE_PHP environment variable if it is available. + php="$EE_PHP" +else + # Default to using the php that we find on the PATH. + # Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 + php="`which php`" +fi + +# Build the path to the root PHP file +SCRIPT_PATH="$(dirname "$SELF_PATH")/../php/boot-fs.php" +case $("$php" -r "echo PHP_OS;") in + WINNT*) + SCRIPT_PATH="$(cygpath -w -a -- "$SCRIPT_PATH")" ;; +esac + +# Pass in the path to php so that ee knows which one +# to use if it re-launches itself to run other commands. +export EE_PHP_USED="$php" + +exec "$php" $EE_PHP_ARGS "$SCRIPT_PATH" "$@" diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..2d14743d9 --- /dev/null +++ b/composer.json @@ -0,0 +1,88 @@ +{ + "name": "easyengine/easyengine", + "description": "The command line interface for EasyEngine", + "keywords": [ + "cli", + "easyengine" + ], + "homepage": "https://easyengine.io", + "license": "MIT", + "support": { + "issues": "https://github.com/easyengine/easyengine/issues", + "source": "https://github.com/easyengine/easyengine", + "docs": "https://easyengine.io/docs/" + }, + "bin": [ + "bin/ee" + ], + "config": { + "platform": { + "php": "7.3" + }, + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=7.0", + "composer/composer": "2.2.24", + "composer/semver": "3.2.4", + "daverandom/libdns": "^2.0", + "easyengine/admin-tools-command": "v1.1.1", + "easyengine/auth-command": "v1.2.1", + "easyengine/config-command": "v1.1.0", + "easyengine/cron-command": "v2.1.0", + "easyengine/dash-command": "v1.0.1", + "easyengine/log-command": "v1.1.0", + "easyengine/mailhog-command": "v1.0.3", + "easyengine/service-command": "v1.6.2", + "easyengine/shell-command": "v1.1.3", + "easyengine/site-command": "v3.7.2", + "easyengine/site-type-php": "v1.10.0", + "easyengine/site-type-wp": "v1.10.0", + "monolog/monolog": "1.24.0", + "mustache/mustache": "2.14.1", + "rmccue/requests": "^2.0", + "symfony/config": "4.4.*", + "symfony/console": "4.4.*", + "symfony/debug": "4.4.*", + "symfony/dependency-injection": "4.4.*", + "symfony/event-dispatcher": "4.4.*", + "symfony/filesystem": "4.4.*", + "symfony/finder": "4.4.*", + "symfony/process": "5.4.*", + "symfony/translation": "4.4.*", + "symfony/yaml": "4.4.*", + "wp-cli/mustangostang-spyc": "0.6.3", + "wp-cli/php-cli-tools": "0.11.10" + }, + "require-dev": { + "behat/behat": "3.8.1", + "dealerdirect/phpcodesniffer-composer-installer": "0.7.0", + "phpunit/phpunit": "9.5.*", + "phpcompatibility/php-compatibility": "8.2.0", + "wp-coding-standards/wpcs": "0.13.1" + }, + "suggest": { + "psy/psysh": "Enhanced shell functionality" + }, + "autoload": { + "psr-0": { + "EE\\": "php/" + }, + "classmap": [ + "php/class-ee.php", + "php/class-ee-db.php", + "php/class-ee-docker.php", + "php/class-ee-command.php" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..dd55d7f6d --- /dev/null +++ b/composer.lock @@ -0,0 +1,6551 @@ +{ + "_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": "56f7965b03832415cb0cc5a7abd55e32", + "packages": [ + { + "name": "acmephp/core", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/acmephp/core.git", + "reference": "cd4e69385052b05f24cc4aa938b3bad0a033d085" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/acmephp/core/zipball/cd4e69385052b05f24cc4aa938b3bad0a033d085", + "reference": "cd4e69385052b05f24cc4aa938b3bad0a033d085", + "shasum": "" + }, + "require": { + "acmephp/ssl": "^1.0", + "ext-hash": "*", + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "guzzlehttp/psr7": "^1.0", + "php": ">=5.5.0", + "psr/http-message": "^1.0", + "psr/log": "^1.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.22", + "symfony/process": "^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "AcmePhp\\Core\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com", + "homepage": "http://titouangalopin.com" + }, + { + "name": "Jérémy Derussé", + "homepage": "https://twitter.com/jderusse" + } + ], + "description": "Raw implementation of the ACME protocol in PHP", + "homepage": "https://github.com/acmephp/core", + "keywords": [ + "acmephp", + "certificate", + "csr", + "encryption", + "https", + "letsencrypt", + "openssl", + "ssl", + "x509" + ], + "support": { + "source": "https://github.com/acmephp/core/tree/1.3.0" + }, + "time": "2020-12-13T21:29:40+00:00" + }, + { + "name": "acmephp/ssl", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/acmephp/ssl.git", + "reference": "30a02abf8811978ad1837a0c98383438d4c91f78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/acmephp/ssl/zipball/30a02abf8811978ad1837a0c98383438d4c91f78", + "reference": "30a02abf8811978ad1837a0c98383438d4c91f78", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-openssl": "*", + "lib-openssl": ">=0.9.8", + "php": ">=5.5.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.22" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "AcmePhp\\Ssl\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com", + "homepage": "http://titouangalopin.com" + }, + { + "name": "Jérémy Derussé", + "homepage": "https://twitter.com/jderusse" + } + ], + "description": "PHP wrapper around OpenSSL extension providing SSL encoding, decoding, parsing and signing features", + "homepage": "https://github.com/acmephp/ssl", + "keywords": [ + "ECDSA", + "acmephp", + "certificate", + "csr", + "https", + "openssl", + "rsa", + "ssl", + "x509" + ], + "support": { + "issues": "https://github.com/acmephp/ssl/issues", + "source": "https://github.com/acmephp/ssl/tree/1.3.0" + }, + "time": "2020-12-13T21:27:31+00:00" + }, + { + "name": "cloudflare/sdk", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/cloudflare/cloudflare-php.git", + "reference": "2d3f198773e865b5de2357d7bdbc52bdf42e8f97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cloudflare/cloudflare-php/zipball/2d3f198773e865b5de2357d7bdbc52bdf42e8f97", + "reference": "2d3f198773e865b5de2357d7bdbc52bdf42e8f97", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^7.0.1", + "php": ">=7.2.5", + "psr/http-message": "~1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.6", + "phpmd/phpmd": "@stable", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cloudflare\\API\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Junade Ali", + "email": "junade@cloudflare.com" + } + ], + "description": "PHP binding for v4 of the Cloudflare Client API.", + "support": { + "issues": "https://github.com/cloudflare/cloudflare-php/issues", + "source": "https://github.com/cloudflare/cloudflare-php/tree/1.4.0" + }, + "time": "2024-12-17T23:18:20+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.5.10", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/961a5e4056dd2e4a2eedcac7576075947c28bf63", + "reference": "961a5e4056dd2e4a2eedcac7576075947c28bf63", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.10" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-12-08T15:06:51+00:00" + }, + { + "name": "composer/composer", + "version": "2.2.24", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "91d9d38ebc274267f952ee1fd3892dc7962075f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/91d9d38ebc274267f952ee1fd3892dc7962075f4", + "reference": "91d9d38ebc274267f952ee1fd3892dc7962075f4", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^1.0", + "composer/semver": "^3.0", + "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^2.0 || ^3.0", + "justinrainbow/json-schema": "^5.2.11", + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0 || ^2.0", + "react/promise": "^1.2 || ^2.7", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0", + "symfony/filesystem": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpspec/prophecy": "^1.10", + "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.2-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "source": "https://github.com/composer/composer/tree/2.2.24" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-06-10T20:51:52+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/67a32d7d6f9f560b726ab25a061b38ff3a80c560", + "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/1.0.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-21T20:24:37+00:00" + }, + { + "name": "composer/semver", + "version": "3.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-13T08:59:24+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.9", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2025-05-12T21:07:07+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "daverandom/libdns", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "easyengine/admin-tools-command", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/admin-tools-command.git", + "reference": "2bdafc7d683424a156c00b5c82a4ac45f6797c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/admin-tools-command/zipball/2bdafc7d683424a156c00b5c82a4ac45f6797c74", + "reference": "2bdafc7d683424a156c00b5c82a4ac45f6797c74", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "admin-tools", + "admin-tools enable", + "admin-tools disable" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "admin-tools-command.php" + ], + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Command to manage admin tools for php based sites.", + "homepage": "https://github.com/easyengine/amdin-tools", + "support": { + "issues": "https://github.com/EasyEngine/admin-tools-command/issues", + "source": "https://github.com/EasyEngine/admin-tools-command/tree/v1.1.1" + }, + "time": "2025-12-26T10:05:26+00:00" + }, + { + "name": "easyengine/auth-command", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/auth-command.git", + "reference": "009381e270ef415ad2b2f8a0da678459cb9bf4c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/auth-command/zipball/009381e270ef415ad2b2f8a0da678459cb9bf4c9", + "reference": "009381e270ef415ad2b2f8a0da678459cb9bf4c9", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "auth", + "auth create", + "auth delete", + "auth list", + "auth update" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "auth-command.php", + "src/auth-utils.php" + ], + "psr-4": { + "": "src/", + "EE\\Model\\": "src/db/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Configure HTTP Authentication and whitelisting for EasyEngine site", + "homepage": "https://github.com/easyengine/auth-command", + "support": { + "issues": "https://github.com/EasyEngine/auth-command/issues", + "source": "https://github.com/EasyEngine/auth-command/tree/v1.2.1" + }, + "time": "2024-08-13T12:39:42+00:00" + }, + { + "name": "easyengine/config-command", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/config-command.git", + "reference": "78bf37e1161bd6449270a108c309d686849fb723" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/config-command/zipball/78bf37e1161bd6449270a108c309d686849fb723", + "reference": "78bf37e1161bd6449270a108c309d686849fb723", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "config", + "config set", + "config get", + "config unset", + "config list" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "config-command.php" + ], + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Manages EasyEngine configuration", + "homepage": "https://github.com/easyengine/config-command", + "support": { + "issues": "https://github.com/EasyEngine/config-command/issues", + "source": "https://github.com/EasyEngine/config-command/tree/v1.1.0" + }, + "time": "2025-09-27T06:24:03+00:00" + }, + { + "name": "easyengine/cron-command", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/cron-command.git", + "reference": "423d990c227a3e070f0fbbdd576a427ba4ad00f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/cron-command/zipball/423d990c227a3e070f0fbbdd576a427ba4ad00f5", + "reference": "423d990c227a3e070f0fbbdd576a427ba4ad00f5", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "cron", + "cron create", + "cron delete", + "cron update", + "cron list", + "cron run-now" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "cron-command.php", + "src/helper/utils.php" + ], + "psr-4": { + "": "src/", + "EE\\Model\\": "src/db/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Manages cron jobs in EasyEngine", + "homepage": "https://github.com/easyengine/cron-command", + "support": { + "issues": "https://github.com/EasyEngine/cron-command/issues", + "source": "https://github.com/EasyEngine/cron-command/tree/v2.1.0" + }, + "time": "2024-05-27T13:06:26+00:00" + }, + { + "name": "easyengine/dash-command", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/dash-command.git", + "reference": "ad84e936af9f28d19e911c330752e5ebe282cc5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/dash-command/zipball/ad84e936af9f28d19e911c330752e5ebe282cc5d", + "reference": "ad84e936af9f28d19e911c330752e5ebe282cc5d", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "dash", + "dash init" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "dash-command.php" + ], + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Command to manage dash", + "homepage": "https://github.com/easyengine/dash-command", + "support": { + "issues": "https://github.com/EasyEngine/dash-command/issues", + "source": "https://github.com/EasyEngine/dash-command/tree/v1.0.1" + }, + "time": "2025-12-11T04:42:36+00:00" + }, + { + "name": "easyengine/log-command", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/log-command.git", + "reference": "192a5a9dcdcf3f65aa3af3f9dbb6200e026e5c6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/log-command/zipball/192a5a9dcdcf3f65aa3af3f9dbb6200e026e5c6c", + "reference": "192a5a9dcdcf3f65aa3af3f9dbb6200e026e5c6c", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "log show" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "log-command.php" + ], + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "https://github.com/easyengine/log-command", + "support": { + "issues": "https://github.com/EasyEngine/log-command/issues", + "source": "https://github.com/EasyEngine/log-command/tree/v1.1.0" + }, + "time": "2021-06-16T14:24:11+00:00" + }, + { + "name": "easyengine/mailhog-command", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/mailhog-command.git", + "reference": "1dee0473dfb7011a15af7dae549794d7ab9e2946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/mailhog-command/zipball/1dee0473dfb7011a15af7dae549794d7ab9e2946", + "reference": "1dee0473dfb7011a15af7dae549794d7ab9e2946", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "mailhog", + "mailhog enable", + "mailhog disable", + "mailhog status" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "mailhog-command.php" + ], + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Command to manage mailhog", + "homepage": "https://github.com/easyengine/mailhog-command", + "support": { + "issues": "https://github.com/EasyEngine/mailhog-command/issues", + "source": "https://github.com/EasyEngine/mailhog-command/tree/v1.0.3" + }, + "time": "2022-05-31T11:42:28+00:00" + }, + { + "name": "easyengine/service-command", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/service-command.git", + "reference": "d6f642d26b019bee68b3f23f3ccb59c2ee4266ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/service-command/zipball/d6f642d26b019bee68b3f23f3ccb59c2ee4266ae", + "reference": "d6f642d26b019bee68b3f23f3ccb59c2ee4266ae", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "service", + "service enable", + "service disable", + "service restart", + "service reload" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helper/service-utils.php", + "service-command.php" + ], + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Command to manager global containers/services in EasyEngine", + "homepage": "https://github.com/easyengine/service-command", + "support": { + "issues": "https://github.com/EasyEngine/service-command/issues", + "source": "https://github.com/EasyEngine/service-command/tree/v1.6.2" + }, + "time": "2025-09-04T07:03:26+00:00" + }, + { + "name": "easyengine/shell-command", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/shell-command.git", + "reference": "e16751f54ae98ac2d43d54add02fa8befd568f9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/shell-command/zipball/e16751f54ae98ac2d43d54add02fa8befd568f9c", + "reference": "e16751f54ae98ac2d43d54add02fa8befd568f9c", + "shasum": "" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "shell" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "shell-command.php" + ], + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Shell to run helpful commands inside containers.", + "homepage": "https://github.com/easyengine/shell-command", + "support": { + "issues": "https://github.com/EasyEngine/shell-command/issues", + "source": "https://github.com/EasyEngine/shell-command/tree/v1.1.3" + }, + "time": "2024-11-16T07:30:59+00:00" + }, + { + "name": "easyengine/site-command", + "version": "v3.7.2", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/site-command.git", + "reference": "092418cbcbe448b49fb599d60d86ed2f5f93a411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/site-command/zipball/092418cbcbe448b49fb599d60d86ed2f5f93a411", + "reference": "092418cbcbe448b49fb599d60d86ed2f5f93a411", + "shasum": "" + }, + "require": { + "acmephp/core": "1.3.0", + "cloudflare/sdk": "^1.3", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^7.4", + "league/flysystem": "1.1.4", + "symfony/serializer": "3.4.20", + "webmozart/assert": "1.10.0" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "site create --type=html", + "site delete", + "site update", + "site info --type=html", + "site enable", + "site disable", + "site info", + "site ssl", + "site list", + "site reload --type=html", + "site restart --type=html", + "site share", + "site clean" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "site-command.php", + "src/helper/hooks.php", + "src/helper/site-utils.php", + "src/helper/class-ee-site.php", + "src/helper/SimpleDnsCloudflareSolver.php", + "src/helper/Site_Self_Signed.php", + "src/helper/Site_Letsencrypt.php", + "src/helper/Site_Backup_Restore.php", + "src/helper/Shutdown_Handler.php", + "src/clone/Cloner.php", + "src/clone/clone-utils.php" + ], + "psr-4": { + "": "src/", + "EE\\Model\\": "src/db/", + "AcmePhp\\Cli\\": "AcmePhp/Cli", + "EE\\Site\\Type\\": "src/site-type/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "https://github.com/easyengine/site-command", + "support": { + "issues": "https://github.com/EasyEngine/site-command/issues", + "source": "https://github.com/EasyEngine/site-command/tree/v3.7.2" + }, + "time": "2025-12-26T10:03:57+00:00" + }, + { + "name": "easyengine/site-type-php", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/site-type-php.git", + "reference": "f91ddb15b0353a9e4e15bc53b5dbbdf98b2c7b50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/site-type-php/zipball/f91ddb15b0353a9e4e15bc53b5dbbdf98b2c7b50", + "reference": "f91ddb15b0353a9e4e15bc53b5dbbdf98b2c7b50", + "shasum": "" + }, + "require-dev": { + "wp-cli/mustangostang-spyc": "^0.6.3" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "site create --type=php", + "site delete", + "site update", + "site info --type=php", + "site enable", + "site disable", + "site info", + "site ssl", + "site list", + "site reload --type=php", + "site restart --type=php", + "site share", + "site clean" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "site-type-php.php" + ], + "psr-4": { + "EE\\Site\\Type\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "EasyEngine package for PHP site creation.", + "homepage": "https://github.com/easyengine/site-type-php", + "support": { + "issues": "https://github.com/EasyEngine/site-type-php/issues", + "source": "https://github.com/EasyEngine/site-type-php/tree/v1.10.0" + }, + "time": "2025-12-11T09:40:43+00:00" + }, + { + "name": "easyengine/site-type-wp", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/EasyEngine/site-type-wp.git", + "reference": "1dffdfffdd45d188a069144fd8e8969508c29101" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyEngine/site-type-wp/zipball/1dffdfffdd45d188a069144fd8e8969508c29101", + "reference": "1dffdfffdd45d188a069144fd8e8969508c29101", + "shasum": "" + }, + "require-dev": { + "wp-cli/mustangostang-spyc": "^0.6.3" + }, + "type": "ee-cli-package", + "extra": { + "bundled": true, + "commands": [ + "site create --type=wp", + "site delete", + "site update", + "site info --type=wp", + "site enable", + "site disable", + "site info", + "site ssl", + "site list", + "site reload --type=wp", + "site restart --type=wp", + "site share", + "site clean" + ], + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "site-type-wp.php" + ], + "psr-4": { + "EE\\Site\\Type\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "EasyEngine site type package for WordPress site creation.", + "homepage": "https://github.com/easyengine/site-type-wp", + "support": { + "issues": "https://github.com/EasyEngine/site-type-wp/issues", + "source": "https://github.com/EasyEngine/site-type-wp/tree/v1.10.0" + }, + "time": "2025-12-11T09:41:04+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4152d9eb85c445fe1f992001d1748e8bec070d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4152d9eb85c445fe1f992001d1748e8bec070d2", + "reference": "f4152d9eb85c445fe1f992001d1748e8bec070d2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^1.9.1 || ^2.6.3", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2024-07-18T11:12:18+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-04-17T16:00:37+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.3.1", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20", + "reference": "b5a44b6391a3bbb75c9f2b73e1ef03d6045e1e20", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.1" + }, + "time": "2025-12-12T08:56:22+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.4" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2021-06-23T21:56:05+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "c7f2872fb273bf493811473dafc88d60ae829f48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/c7f2872fb273bf493811473dafc88d60ae829f48", + "reference": "c7f2872fb273bf493811473dafc88d60ae829f48", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.12.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2023-08-03T07:14:11+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.24.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/1.24.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2018-11-05T09:00:11+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.14.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "reference": "579ffa5c96e1d292c060b3dd62811ff01ad8c24e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.1" + }, + "time": "2022-01-21T06:08:36+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "react/promise", + "version": "v2.11.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.11.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-16T16:16:50+00:00" + }, + { + "name": "rmccue/requests", + "version": "v2.0.17", + "source": { + "type": "git", + "url": "https://github.com/WordPress/Requests.git", + "reference": "74d1648cc34e16a42ea25d548fc73ec107a90421" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/Requests/zipball/74d1648cc34e16a42ea25d548fc73ec107a90421", + "reference": "74d1648cc34e16a42ea25d548fc73ec107a90421", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", + "php-parallel-lint/php-console-highlighter": "^0.5.0", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "requests/test-server": "dev-main", + "squizlabs/php_codesniffer": "^3.6", + "wp-coding-standards/wpcs": "^2.0", + "yoast/phpunit-polyfills": "^1.1.5" + }, + "suggest": { + "art4/requests-psr18-adapter": "For using Requests as a PSR-18 HTTP Client", + "ext-curl": "For improved performance", + "ext-openssl": "For secure transport support", + "ext-zlib": "For improved performance when decompressing encoded streams" + }, + "type": "library", + "autoload": { + "files": [ + "library/Deprecated.php" + ], + "psr-4": { + "WpOrg\\Requests\\": "src/" + }, + "classmap": [ + "library/Requests.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "https://rmccue.io/" + }, + { + "name": "Alain Schlesser", + "homepage": "https://github.com/schlessera" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl" + }, + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/Requests/graphs/contributors" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "https://requests.ryanmccue.info/", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "support": { + "docs": "https://requests.ryanmccue.info/", + "issues": "https://github.com/WordPress/Requests/issues", + "source": "https://github.com/WordPress/Requests" + }, + "time": "2025-12-12T17:47:19+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "symfony/config", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658", + "reference": "ed42f8f9da528d2c6cae36fe1f380b0c1d8f0658", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/filesystem": "^3.4|^4.0|^5.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^3.4|^4.0|^5.0", + "symfony/finder": "^3.4|^4.0|^5.0", + "symfony/messenger": "^4.1|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T09:59:04+00:00" + }, + { + "name": "symfony/console", + "version": "v4.4.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", + "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/console/tree/v4.4.49" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-05T17:10:16+00:00" + }, + { + "name": "symfony/debug", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", + "reference": "1a692492190773c5310bc7877cb590c04c2f05be", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "^3.4|^4.0|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "abandoned": "symfony/error-handler", + "time": "2022-07-28T16:29:46+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v4.4.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9065fe97dbd38a897e95ea254eb5ddfe1310f734", + "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "psr/container": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1.6|^2" + }, + "conflict": { + "symfony/config": "<4.3|>=5.0", + "symfony/finder": "<3.4", + "symfony/proxy-manager-bridge": "<3.4", + "symfony/yaml": "<4.4.26" + }, + "provide": { + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" + }, + "require-dev": { + "symfony/config": "^4.3", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/yaml": "^4.4.26|^5.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v4.4.49" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-16T16:18:09+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1e866e9e5c1b22168e0ce5f0b467f19bba61266a", + "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T09:59:04+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/761c8b8387cfe5f8026594a75fdf0a4e83ba6974", + "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.10.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T09:59:04+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v4.4.42", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/815412ee8971209bd4c1eecd5f4f481eacd44bf5", + "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v4.4.42" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-20T08:49:14+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.4.44", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "66bd787edb5e42ff59d3523f623895af05043e4f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/66bd787edb5e42ff59d3523f623895af05043e4f", + "reference": "66bd787edb5e42ff59d3523f623895af05043e4f", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v4.4.44" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:35:46+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-06T11:36:42+00:00" + }, + { + "name": "symfony/serializer", + "version": "v3.4.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "40b326ca34c113da5d626e87fb5d0627b8324098" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/40b326ca34c113da5d626e87fb5d0627b8324098", + "reference": "40b326ca34c113da5d626e87fb5d0627b8324098", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "phpdocumentor/type-resolver": "<0.2.1", + "symfony/dependency-injection": "<3.2", + "symfony/property-access": ">=3.0,<3.0.4|>=2.8,<2.8.4", + "symfony/property-info": "<3.1", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "symfony/cache": "~3.1|~4.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.2|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/property-access": "~2.8|~3.0|~4.0", + "symfony/property-info": "~3.1|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", + "doctrine/cache": "For using the default cached annotation reader and metadata cache.", + "psr/cache-implementation": "For using the metadata cache.", + "symfony/config": "For using the XML mapping loader.", + "symfony/http-foundation": "To use the DataUriNormalizer.", + "symfony/property-access": "For using the ObjectNormalizer.", + "symfony/property-info": "To deserialize relations.", + "symfony/yaml": "For using the default YAML mapping loader." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Serializer Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/3.4" + }, + "time": "2018-12-02T13:20:19+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/translation", + "version": "v4.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "45036b1d53accc48fe9bab71ccd86d57eba0dd94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/45036b1d53accc48fe9bab71ccd86d57eba0dd94", + "reference": "45036b1d53accc48fe9bab71ccd86d57eba0dd94", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^1.1.6|^2" + }, + "conflict": { + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "symfony/translation-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/finder": "~2.8|~3.0|~4.0|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/intl": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1.2|^2", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v4.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-03T15:15:11+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "450d4172653f38818657022252f9d81be89ee9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/450d4172653f38818657022252f9d81be89ee9a8", + "reference": "450d4172653f38818657022252f9d81be89ee9a8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", + "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v4.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-08-02T15:47:23+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + }, + { + "name": "wp-cli/mustangostang-spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/spyc.git", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/spyc/zipball/6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "reference": "6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "includes/functions.php" + ], + "psr-4": { + "Mustangostang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)", + "homepage": "https://github.com/mustangostang/spyc/", + "support": { + "source": "https://github.com/wp-cli/spyc/tree/autoload" + }, + "time": "2017-04-25T11:26:20+00:00" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.11.10", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "f99308f92487efe18b5aac2057240fe5bb542374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/f99308f92487efe18b5aac2057240fe5bb542374", + "reference": "f99308f92487efe18b5aac2057240fe5bb542374", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "type": "library", + "autoload": { + "files": [ + "lib/cli/cli.php" + ], + "psr-0": { + "cli": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + }, + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "support": { + "issues": "https://github.com/wp-cli/php-cli-tools/issues", + "source": "https://github.com/wp-cli/php-cli-tools/tree/master" + }, + "time": "2018-08-22T10:11:06+00:00" + } + ], + "packages-dev": [ + { + "name": "behat/behat", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "fbb065457d523d9856d4b50775b4151a7598b510" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/fbb065457d523d9856d4b50775b4151a7598b510", + "reference": "fbb065457d523d9856d4b50775b4151a7598b510", + "shasum": "" + }, + "require": { + "behat/gherkin": "^4.6.0", + "behat/transliterator": "^1.2", + "ext-mbstring": "*", + "php": "^7.2 || ^8.0", + "psr/container": "^1.0", + "symfony/config": "^4.4 || ^5.0", + "symfony/console": "^4.4 || ^5.0", + "symfony/dependency-injection": "^4.4 || ^5.0", + "symfony/event-dispatcher": "^4.4 || ^5.0", + "symfony/translation": "^4.4 || ^5.0", + "symfony/yaml": "^4.4 || ^5.0" + }, + "require-dev": { + "container-interop/container-interop": "^1.2", + "herrera-io/box": "~1.6.1", + "phpunit/phpunit": "^8.5 || ^9.0", + "symfony/process": "^4.4 || ^5.0" + }, + "suggest": { + "ext-dom": "Needed to output test results in JUnit format." + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Behat\\": "src/Behat/Behat/", + "Behat\\Testwork\\": "src/Behat/Testwork/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Scenario-oriented BDD framework for PHP", + "homepage": "http://behat.org/", + "keywords": [ + "Agile", + "BDD", + "ScenarioBDD", + "Scrum", + "StoryBDD", + "User story", + "business", + "development", + "documentation", + "examples", + "symfony", + "testing" + ], + "support": { + "issues": "https://github.com/Behat/Behat/issues", + "source": "https://github.com/Behat/Behat/tree/v3.8.1" + }, + "time": "2020-11-07T15:55:18+00:00" + }, + { + "name": "behat/gherkin", + "version": "v4.10.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6", + "reference": "cbb83c4c435dd8d05a161f2a5ae322e61b2f4db6", + "shasum": "" + }, + "require": { + "php": "~7.2|~8.0" + }, + "require-dev": { + "cucumber/cucumber": "dev-gherkin-24.1.0", + "phpunit/phpunit": "~8|~9", + "symfony/yaml": "~3|~4|~5|~6|~7" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "support": { + "issues": "https://github.com/Behat/Gherkin/issues", + "source": "https://github.com/Behat/Gherkin/tree/v4.10.0" + }, + "time": "2024-10-19T14:46:06+00:00" + }, + { + "name": "behat/transliterator", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/baac5873bac3749887d28ab68e2f74db3a4408af", + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "chuyskywalker/rolling-curl": "^3.1", + "php-yaoi/php-yaoi": "^1.0", + "phpunit/phpunit": "^8.5.25 || ^9.5.19" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Transliterator\\": "src/Behat/Transliterator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "support": { + "issues": "https://github.com/Behat/Transliterator/issues", + "source": "https://github.com/Behat/Transliterator/tree/v1.5.0" + }, + "abandoned": true, + "time": "2022-03-30T09:27:43+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.0", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "e8d808670b8f882188368faaf1144448c169c0b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e8d808670b8f882188368faaf1144448c169c0b7", + "reference": "e8d808670b8f882188368faaf1144448c169c0b7", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2 || ^3 || 4.0.x-dev" + }, + "require-dev": { + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2020-06-25T14:57:39+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.19.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.1" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.5" + }, + "time": "2025-12-06T11:45:25+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpcompatibility/php-compatibility", + "version": "8.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "eaf613c1a8265bcfd7b0ab690783f2aef519f78a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/eaf613c1a8265bcfd7b0ab690783f2aef519f78a", + "reference": "eaf613c1a8265bcfd7b0ab690783f2aef519f78a", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "PHPCompatibility\\": "PHPCompatibility/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, + "time": "2018-07-17T13:42:26+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.32", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:23:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.28", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-01-14T12:32:24+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2025-08-10T06:51:50+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:03:27+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:10:35+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T06:57:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.13.5", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" + } + ], + "time": "2025-11-04T16:30:35+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "0.13.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "1f64b1a0b5b789822d0303436ee4e30e0135e4dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/1f64b1a0b5b789822d0303436ee4e30e0135e4dc", + "reference": "1f64b1a0b5b789822d0303436ee4e30e0135e4dc", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki" + }, + "time": "2017-08-05T16:08:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=7.0" + }, + "platform-dev": {}, + "platform-overrides": { + "php": "7.3" + }, + "plugin-api-version": "2.6.0" +} diff --git a/config/bash_completion.d/ee_auto.rc b/config/bash_completion.d/ee_auto.rc deleted file mode 100644 index c771dfff9..000000000 --- a/config/bash_completion.d/ee_auto.rc +++ /dev/null @@ -1,384 +0,0 @@ -_ee_complete() -{ - local cur prev BASE_LEVEL - - COMPREPLY=() - cur=${COMP_WORDS[COMP_CWORD]} - prev=${COMP_WORDS[COMP_CWORD-1]} - mprev=${COMP_WORDS[COMP_CWORD-2]} - - - # SETUP THE BASE LEVEL (everything after "ee") - if [ $COMP_CWORD -eq 1 ]; then - COMPREPLY=( $(compgen \ - -W "stack site debug clean secure import-slow-log log update sync info --version --help --quiet" \ - -- $cur) ) - - - # SETUP THE SECOND LEVEL (EVERYTHING AFTER "ee second") - elif [ $COMP_CWORD -eq 2 ]; then - case "$prev" in - - # HANDLE EVERYTHING AFTER THE SECOND LEVEL NAMESPACE - "clean") - COMPREPLY=( $(compgen \ - -W "--memcache --opcache --fastcgi --redis --all" \ - -- $cur) ) - ;; - - # IF YOU HAD ANOTHER CONTROLLER, YOU'D HANDLE THAT HERE - "debug") - COMPREPLY=( $(compgen \ - -W "$(command find /etc/nginx/sites-enabled/ -type l -printf "%P " 2> /dev/null) --nginx --php --php7 --fpm --fpm7 --mysql -i --interactive --all --import-slow-log --import-slow-log-interval= --nginx=off --php=off --php7=off --fpm=off --fpm7=off --mysql=off --all=off " \ - -- $cur) ) - ;; - - "stack") - COMPREPLY=( $(compgen \ - -W "upgrade install purge reload remove restart start status stop migrate" \ - -- $cur) ) - ;; - - "site") - COMPREPLY=( $(compgen \ - -W "cd create delete disable edit enable info list log show update" \ - -- $cur) ) - ;; - - "secure") - COMPREPLY=( $(compgen \ - -W "--auth --port --ip" \ - -- $cur) ) - ;; - - "info") - COMPREPLY=( $(compgen \ - -W "--mysql --php --php7 --nginx" \ - -- $cur) ) - ;; - - "log") - COMPREPLY=( $(compgen \ - -W "show reset gzip mail" \ - -- $cur) ) - ;; - - # EVERYTHING ELSE - *) - ;; - esac - - # SETUP THE THIRD LEVEL (EVERYTHING AFTER "ee second third") - elif [ $COMP_CWORD -eq 3 ]; then - case "$prev" in - # HANDLE EVERYTHING AFTER THE THIRD LEVEL NAMESPACE - "install" | "purge" | "remove" ) - COMPREPLY=( $(compgen \ - -W "--web --admin --mail --nginx --php --php7 --mysql --postfix --wpcli --phpmyadmin --adminer --utils --all --mailscanner --hhvm --redis --phpredisadmin" \ - -- $cur) ) - ;; - "upgrade" ) - COMPREPLY=( $(compgen \ - -W "--web --mail --nginx --php --php7 --mysql --postfix --all --hhvm --php56 --no-prompt --wpcli" \ - -- $cur) ) - ;; - "start" | "stop" | "reload" | "restart" | "status") - COMPREPLY=( $(compgen \ - -W "--nginx --php --php7 --mysql --postfix --memcache --dovecot --redis" \ - -- $cur) ) - ;; - "migrate") - COMPREPLY=( $(compgen \ - -W "--mariadb" \ - -- $cur) ) - ;; - "list") - COMPREPLY=( $(compgen \ - -W "--enabled --disabled" \ - -- $cur) ) - ;; - - "edit" | "enable" | "info" | "log" | "show" | "cd" | "delete") - if [ ${COMP_WORDS[1]} == "log" ]; then - COMPREPLY=( $(compgen \ - -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --nginx --php --fpm --mysql --access" \ - -- $cur) ) - else - COMPREPLY=( $(compgen \ - -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null)" \ - -- $cur) ) - fi - ;; - "update") - COMPREPLY=( $(compgen \ - -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --all" \ - -- $cur) ) - ;; - "gzip") - COMPREPLY=( $(compgen \ - -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --nginx --php --fpm --mysql --access" \ - -- $cur) ) - ;; - - "mail") - COMPREPLY=( $(compgen \ - -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --nginx --php --fpm --mysql --access --to=" \ - -- $cur) ) - ;; - - "reset") - COMPREPLY=( $(compgen \ - -W "$(find /etc/nginx/sites-available/ -type f -printf "%P " 2> /dev/null) --nginx --php --fpm --mysql --wp --access --slow-log-db" \ - -- $cur) ) - ;; - - "disable") - COMPREPLY=( $(compgen \ - -W "$(command find /etc/nginx/sites-enabled/ -type l -printf "%P " 2> /dev/null)" \ - -- $cur) ) - ;; - - *) - ;; - esac - - if [ ${COMP_WORDS[1]} == "debug" ] && ([ "$prev" != "--start" ] && [ "$prev" != "--nginx" ] && [ "$prev" != "--php" ] && [ "$prev" != "--php7" ] && [ "$prev" != "--fpm" ] && [ "$prev" != "--fpm7" ] && [ "$prev" != "--mysql" ] && [ "$prev" != "-i" ] && [ "$prev" != "--interactive" ] && [ "$prev" != "--import-slow-log" ] && [ "$prev" != "--stop" ]); then - retlist="--all --wp --rewrite -i --all=off --wp=off --rewrite=off" - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - fi - - #if [ ${COMP_WORDS[1]} == "log" ] && ([ "$prev" != "--access" ] || [ "$prev" != "--nginx" ] || [ "$prev" != "--php" ] || [ "$prev" != "--fpm" ] || [ "$prev" != "--mysql" ] || [ "$prev" != "-i" ] || ["$prev" != "--interactive" ] || ["$prev" != "--stop" ]); then - # retlist="--all --wp --rewrite -i --all=off --wp=off --rewrite=off" - # ret="${retlist[@]/$prev}" - # COMPREPLY=( $(compgen \ - # -W "$(echo $ret)" \ - # -- $cur) ) - #fi - - - elif [ $COMP_CWORD -eq 4 ]; then - case "$mprev" in - # HANDLE EVERYTHING AFTER THE THIRD LEVEL NAMESPACE - - "create") - COMPREPLY=( $(compgen \ - -W "--user --pass --email --html --php --php7 --mysql --wp --wpsubdir --wpsubdomain --w3tc --wpfc --wpsc --hhvm --proxy= --wpredis --letsencrypt -le" \ - -- $cur) ) - ;; - - "update") - COMPREPLY=( $(compgen \ - -W "--password --php --php7 --mysql --wp --wpsubdir --wpsubdomain --w3tc --wpfc --wpsc --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" \ - -- $cur) ) - ;; - "delete") - COMPREPLY=( $(compgen \ - -W "--db --files --all --no-prompt --force -f" \ - -- $cur) ) - ;; - "show") - COMPREPLY=( $(compgen \ - -W "--wp --nginx --php --fpm --mysql --access" \ - -- $cur) ) - ;; - - "gzip") - COMPREPLY=( $(compgen \ - -W "--wp --nginx --php --fpm --mysql --access" \ - -- $cur) ) - ;; - - "mail") - COMPREPLY=( $(compgen \ - -W "--wp --nginx --php --fpm --mysql --access --to=" \ - -- $cur) ) - ;; - - "reset") - COMPREPLY=( $(compgen \ - -W "--wp --nginx --php --fpm --mysql --wp --access --slow-log-db" \ - -- $cur) ) - ;; - edit) - COMPREPLY=( $(compgen \ - - -- $cur) ) - ;; - *) - ;; - esac - - fi - - case "$prev" in - "--wp") - if [ ${COMP_WORDS[1]} != "debug" ]; then - if [ ${COMP_WORDS[2]} == "create" ]; then - retlist="--wp --wpsc --w3tc --wpfc --hhvm --user --email --pass --wpredis --letsencrypt --php7" - elif [ ${COMP_WORDS[2]} == "update" ]; then - retlist="--wp --w3tc --wpfc --wpsc --php7 --php7=off --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" - else - retlist="" - fi - else - retlist="--wp --wp=off --rewrite --rewrite=off -i --interactive" - fi - - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - - "--wpsubdir" | "--wpsubdomain") - if [ ${COMP_WORDS[1]} != "debug" ]; then - if [ ${COMP_WORDS[2]} == "create" ]; then - retlist="--wpsc --w3tc --wpfc --hhvm --user --email --pass --wpredis --letsencrypt --php7" - elif [ ${COMP_WORDS[2]} == "update" ]; then - retlist="--w3tc --wpfc --wpsc --php7 --php7=off --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" - else - retlist="" - fi - else - retlist="--wp=off --rewrite --rewrite=off -i --interactive" - fi - - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - - "--hhvm" | "--wpredis" | "--w3tc" | "--wpfc" | "--wpsc" | "--wpsubdir" | "--wpsubdomain" | "--user" | "--pass" | "--email" | "--wp") - if [ ${COMP_WORDS[2]} == "create" ]; then - retlist="--user --pass --email --wp --wpsubdir --wpsubdomain --w3tc --wpfc --wpsc --hhvm --experimenal --wpredis --php7 --letsencrypt " - else - retlist="" - fi - - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - - "--hhvm" | "--wpredis" | "--w3tc" | "--wpfc") - if [ ${COMP_WORDS[2]} == "update" ]; then - retlist="--password --php --php7 --mysql --wp --wpsubdir --wpsubdomain --w3tc --wpfc --wpsc --hhvm --hhvm=off --experimenal --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" - else - retlist="" - fi - - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - - "--web" | "--admin" | "--mail" | "--nginx" | "--php" | "--php7" | "--mysql" | "--postfix" | "--wpcli" | "--phpmyadmin" | "--adminer" | "--utils" | "--memcache" | "--dovecot" | "--redis | --phpredisadmin") - if [[ ${COMP_WORDS[2]} == "install" || ${COMP_WORDS[2]} == "purge" || ${COMP_WORDS[2]} == "remove" ]]; then - retlist="--web --admin --mail --nginx --php --php7 --mysql --postfix --wpcli --phpmyadmin --adminer --utils --memcache --dovecot --redis --phpredisadmin" - elif [[ ${COMP_WORDS[2]} == "start" || ${COMP_WORDS[2]} == "reload" || ${COMP_WORDS[2]} == "restart" || ${COMP_WORDS[2]} == "stop" ]]; then - retlist="--nginx --php --php7 --mysql --postfix --memcache --dovecot --redis" - elif [[ ${COMP_WORDS[1]} == "debug" ]]; then - retlist="--start --nginx --php --php7 --fpm --fpm7 --mysql -i --interactive -stop --import-slow-log --import-slow-log-interval= -" - if [[ $prev == '--mysql' ]]; then - retlist="--start --nginx --php --php7 --fpm --fpm7 --mysql -i --interactive --stop --import-slow-log" - fi - elif [[ ${COMP_WORDS[1]} == "log" ]]; then - if [ ${COMP_WORDS[2]} == "show" ]; then - retlist="--access --nginx --php --mysql --fpm --wp" - - elif [ ${COMP_WORDS[2]} == "reset" ]; then - retlist="--access --nginx --php --mysql --fpm --wp --slow-log-db" - - elif [ ${COMP_WORDS[2]} == "mail" ]; then - retlist="--access --nginx --php --mysql --fpm --wp --to=" - - fi - fi - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - - "--db" | "--files" | "--force") - retlist="--db --files --all --force" - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - - "--all") - if [ ${COMP_WORDS[1]} == "clean" ]; then - retlist="--memcache --opcache --fastcgi --redis" - elif [ ${COMP_WORDS[2]} == "delete" ]; then - retlist="--db --files --force" - elif [ ${COMP_WORDS[2]} == "update" ]; then - retlist="--password --php --mysql --wp --wpsubdir --wpsubdomain --w3tc --wpfc --wpsc --hhvm --hhvm=off --wpredis --letsencrypt --letsencrypt=off --letsencrypt=renew" - else - retlist="" - fi - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - - "--memcache" | "--opcache" | "--fastcgi" | "--all" | "--redis") - retlist="--memcache --opcache --fastcgi --redis --all" - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - "--auth" | "--port" | "--ip") - retlist="--auth --port --ip" - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - "--access" | "--fpm" | "--wp" | "--slow-log-db") - if [[ ${COMP_WORDS[1]} == "log" ]]; then - if [ ${COMP_WORDS[2]} == "show" ]; then - retlist="--access --nginx --php --mysql --fpm --wp" - - elif [ ${COMP_WORDS[2]} == "reset" ]; then - retlist="--access --nginx --php --mysql --fpm --wp --slow-log-db" - - - elif [ ${COMP_WORDS[2]} == "mail" ]; then - retlist="--access --nginx --php --mysql --fpm --wp --to=" - - fi - fi - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - *) - ;; - esac - case "$mprev" in - "--user" | "--email" | "--pass") - if [ ${COMP_WORDS[2]} == "create" ]; then - retlist="--user --pass --email --html --php --php7 --mysql --wp --wpsubdir --wpsubdomain --w3tc --wpfc --wpsc --hhvm --wpredis --letsencrypt" - fi - ret="${retlist[@]/$prev}" - COMPREPLY=( $(compgen \ - -W "$(echo $ret)" \ - -- $cur) ) - ;; - esac - - return 0 - -} && -complete -F _ee_complete ee diff --git a/config/ee.conf b/config/ee.conf deleted file mode 100644 index 9d94df02a..000000000 --- a/config/ee.conf +++ /dev/null @@ -1,82 +0,0 @@ -# EasyEngine Configuration -# -# All commented values are the application default -# - -[ee] - -### Toggle application level debug (does not toggle framework debugging) -# debug = false - -### Where external (third-party) plugins are loaded from -# plugin_dir = /var/lib/ee/plugins/ - -### Where all plugin configurations are loaded from -# plugin_config_dir = /etc/ee/plugins.d/ - -### Where external templates are loaded from -# template_dir = /var/lib/ee/templates/ - - -[log.logging] - -### Where the log file lives (no log file by default) -file = /var/log/ee/ee.log - -### The level for which to log. One of: info, warn, error, fatal, debug -level = debug - -### Whether or not to log to console -to_console = false - -### Whether or not to rotate the log file when it reaches `max_bytes` -rotate = true - -### Max size in bytes that a log file can grow until it is rotated. -max_bytes = 512000 - -### The maximun number of log files to maintain when rotating -max_files = 7 - -[stack] - -### IP address that will be used in Nginx configurations while installing -ip-address = 127.0.0.1 - -[mysql] - -### MySQL database grant host name -grant-host = localhost - -### Ask for MySQL db name while site creation -db-name = False - -### Ask for MySQL user name while site creation -db-user = False - -[wordpress] - -### Ask for WordPress prefix while site creation -prefix = False - -### User name for WordPress sites -user = - -### Password for WordPress sites -password = - -### EMail for WordPress sites -email = - -[update] - -### If enabled, load a plugin named `update` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true - -[sync] -### If enabled, load a plugin named `update` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/clean.conf b/config/plugins.d/clean.conf deleted file mode 100644 index be8343484..000000000 --- a/config/plugins.d/clean.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[clean] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/debug.conf b/config/plugins.d/debug.conf deleted file mode 100644 index 9ccdf1067..000000000 --- a/config/plugins.d/debug.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[debug] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/import_slow_log.conf b/config/plugins.d/import_slow_log.conf deleted file mode 100644 index d7841dbeb..000000000 --- a/config/plugins.d/import_slow_log.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[import_slow_log] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/info.conf b/config/plugins.d/info.conf deleted file mode 100644 index 59f51a036..000000000 --- a/config/plugins.d/info.conf +++ /dev/null @@ -1,11 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[info] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true - -### Additional plugin configuration settings -foo = bar diff --git a/config/plugins.d/log.conf b/config/plugins.d/log.conf deleted file mode 100644 index a17893f56..000000000 --- a/config/plugins.d/log.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[log] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/secure.conf b/config/plugins.d/secure.conf deleted file mode 100644 index b70f167b0..000000000 --- a/config/plugins.d/secure.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[secure] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/site.conf b/config/plugins.d/site.conf deleted file mode 100644 index 2fb468fe9..000000000 --- a/config/plugins.d/site.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[site] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/stack.conf b/config/plugins.d/stack.conf deleted file mode 100644 index f7bf9b800..000000000 --- a/config/plugins.d/stack.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[stack] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/config/plugins.d/update.conf b/config/plugins.d/update.conf deleted file mode 100644 index 27e9c53a3..000000000 --- a/config/plugins.d/update.conf +++ /dev/null @@ -1,8 +0,0 @@ -### Example Plugin Configuration for EasyEngine - -[update] - -### If enabled, load a plugin named `example` either from the Python module -### `ee.cli.plugins.example` or from the file path -### `/var/lib/ee/plugins/example.py` -enable_plugin = true diff --git a/docs/core-repo-structure.md b/docs/core-repo-structure.md new file mode 100644 index 000000000..f27c31951 --- /dev/null +++ b/docs/core-repo-structure.md @@ -0,0 +1,20 @@ +EasyEngine Core Repository +=== + +The core repository contains the cli interface for EasyEngine and the internal api's to facilitate and accommodate the creation and execution of commands. It is built on top of [WP-CLI](https://github.com/wp-cli/wp-cli) and follows the same basic structure. + +1. The `bin` directory contains the shell script entrypoint to the PHP files. +2. `ci` directory contains shell scripts to automate the phar building and deploying of the same to the [easyengine-builds](https://github.com/easyengine/easyengine-builds) repository with the help of [travis-ci](https://travis-ci.org/). +3. `ee-config` directory has all the `nginx, php and redis` configurations required for different types of sites like `WordPress - Single-Site, Multi-Site` etc. +4. The `php` directory contains the core of EasyEngine cli. + * It contains the `cli-command` which handles the most basic required functions like: `version, update` etc. + * The [WP-CLI internal-api](https://github.com/wp-cli/handbook/blob/master/internal-api.md), booting and auto-loading logic and other utilities. + * EasyEngine adds the following classes on top of the existing codebase: + * `EE_DB` having the generic Sqlite3 functions for db management. + * `EE_DOCKER` having various docker related functions like starting, stopping and inspecting containers, creating and connecting to networks etc. + * Also, the internal-api has been slightly modified to remove WordPress specific functions and additional features like, logging of all the command invocation and related log, success, debug and error messages from `EE::info(), EE::success(), EE::debug(), EE::error()` outputs into a file for debugging purposes. +5. `templates` directory contains the [mustache](https://mustache.github.io/) templates for man page/help, generation of `docker-compose.yml` etc. +6. `utils` directory contains the scripts for `phar` generation. + + + diff --git a/docs/ee.8 b/docs/ee.8 deleted file mode 100644 index 42fa61baa..000000000 --- a/docs/ee.8 +++ /dev/null @@ -1,333 +0,0 @@ -.TH ee 8 "EasyEngine (ee) version: 3.3.8" "Sep 10,2015" "EasyEngine" -.SH NAME -.B EasyEngine (ee) -\- Manage Nginx Based Websites. -.SH SYNOPSIS -ee [ --version | --help | info | stack | site | debug | update | clean | import_slow_log | log | secure | sync] -.TP -ee stack [ install | remove | purge | migrate | upgrade] [ --web | --mail | --all | --nginx | --php | --php7 | --mysql | --admin | --postfix | --mailscanner | --adminer | --redis | --hhvm | --phpmyadmin | --phpredisadmin | --wpcli | --utils ] -.TP -ee stack [ status | start | stop | reload | restart ] [--all | --nginx | --php | --php7 |--mysql | --devcot | --web | --postfix | --memcache | --redis] -.TP -ee site [ list | info | show | enable | disable | edit | cd | show ] [ example.com ] -.TP -ee site create example.com [ --html | --php | --php7 | --mysql] [[--wp | --wpsubdir | --wpsubdomain ] [--wpsc | --w3tc | --wpfc | --wpredis | --hhvm | --letsencrypt/-le]] -.TP -ee site update example.com [ --php | --php7 |--mysql] [[--wp | --wpsubdir | --wpsubdomain ] [--wpsc | --w3tc | --wpfc | --wpredis | --hhvm ] [--password] [--letsencrypt=on/off/renew]] -.TP -ee site delete example.com [--db | --files | --all | --no-prompt | --force/-f ] -.TP -ee debug [ -i | --all=on/off |--nginx=on/off | --rewrite=on/off | --php=on/off | --fpm=on/off | --mysql=on/off ] -.TP -ee debug example.com [ -i | --all=on/off | --nginx=on/off | --rewrite=on/off | --wp=on/off ] -.TP -ee secure [ --auth | --port | --ip ] -.SH DESCRIPTION -EasyEngine aka ee is the opensource project developed with the purpose to automate web-server configuration. -.br -EasyEngine is the collection of python script that provides automation for the web-server -.br -installation, site creation, services debugging & monitoring. -.SH OPTIONS -.TP -.B --version -.br -Display easyengine (ee) version information. -.TP -.B info -.br -ee info - Display Nginx, PHP, MySQL and ee common location information -.br -ee site info - Diplay given website details like enable, disable. weboot and log files. -.TP -.B --help -.br -Display easyengine (ee) help. -.TP -.B stack -.TP -.B install [ --all | --web | --mail | --nginx | --php | --php7 |--mysql | --redis | --postfix | --adminer | --phpmyadmin | --phpredismyadmin | --wpcli | --utils ] -.br -Install Nginx PHP5 MySQL Postfix stack Packages if not used with -.br -any options.Installs specific package if used with option. -.TP -.B remove [ --all | --web | --mail | --nginx | --php | --php7 |--mysql | --redis | --postfix | --adminer | --phpmyadmin | --phpredismyadmin | --wpcli | --utils ] -.br -Remove Nginx PHP5 MySQL Postfix stack Packages if not used with -.br -any options. Remove specific package if used with option. -.TP -.B purge [ --all | --web | --mail | --nginx | --php | --php7 |--mysql | --redis | --postfix | --adminer | --phpmyadmin | --phpredismyadmin | --wpcli | --utils ] -.br -Purge Nginx PHP5 MySQL Postfix stack Packages if not used with any -.br -options.Purge specific package if used with option. -.TP -.B status -.br -Display status of NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server services. -.TP -.B start -.br -Start services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. -.TP -.B stop -.br -Stop services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. -.TP -.B reload -.br -Reload services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. -.TP -.B restart -.br -Restart services NGINX, PHP5-FPM, MySQL, Postfix, Redis-Server. -.TP -.B site -.br -.TP -.B cd [ example.com ] -.br -Change directory to webroot of specified site in subshell. -.TP -.B log [ example.com ] -.br -monitor access and error logs for site specified. -.TP -.B list [ --enabled | --disabled ] -.br -Lists all available sites from /etc/nginx/sites-enabled/ -.br -by default & enable argument. Display sites list from -.br -/etc/nginx/sites-available/ if used with available option. -.TP -.B info [ example.com ] -.br -prints information about site such as access log, error log -.br -location and type of site. -.TP -.B show [ example.com ] -.br -Display NGINX configuration of site. -.TP -.B enable [ example.com ] -.br -Enable site by creating softlink with site file in -.br -/etc/nginx/sites-available to /etc/nginx/sites-enabled/. -.TP -.B disable [ example.com ] -.br -Disable site by Destroying softlink with site file in -.br -/etc/nginx/sites-available to /etc/nginx/sites-enabled/. -.TP -.B edit [ example.com ] -.br -Edit NGINX configuration of site. -.TP -.B create [ example.com ] [ --html | --php | --php7 |--mysql] [[--wp | --wpsubdir | --wpsubdomain ] [--wpsc | --w3tc | --wpfc | --wpredis | --hhvm ]] -.br -Create new site according to given options. If no options provided -.br -create static site with html only. -.TP -.B update [ example.com ] [ --html | --php | --php7 |--mysql] [[--wp | --wpsubdir | --wpsubdomain ] [ --wpsc | --w3tc | --wpfc | --wpredis | --hhvm ] [--password]] -.br -Update site configuration according to specified options. -.TP -.B delete [ example.com ] [--no-prompt ] [--force/-f] [ --db | --files | --all ] -.br -Delete site i.e webroot, database, ad configuration permanently. -.TP -.B debug [ -i | --nginx=on/off | --php=on/off | --php7=on/off | --mysql=on/off | --rewrite=on/off | --fpm=on/off | --fpm7=on/off ] -.br -Starts server level debugging. If this is used without arguments it will start debugging -.br -all services.Else it will debug only service provided with argument.This will Stop -.br -Debugging if used with --all=off argument. -.TP -.B debug example.com [ -i | --nginx=on/off | --rewrite=on/off | --wp=on/off | --all=on/off ] -.br -Starts site level debugging. If this is used without arguments it will start debugging all -.br -services.Else it will debug only service provided with argument.This will Stop Debugging -.br -if used with --all=off argument. -.TP -.B secure [ --auth | --port | --ip ] -.br -Update security settings. -.TP -.B clean [ --fastcgi | --opcache | --memcache | --redis | --all ] -.br -Clean NGINX fastCGI cache, Opcache, Memcache, Redis cache. -.br -Clean NGINX fastCGI cache if no option specified. -.SH ARGUMENTS -.TP -.B -i -.br -setup intractive mode while used with debug. -.TP -.B --nginx=on/off -.br -used with ee debug command. used to start or stop nginx debugging. -.TP -.B --php=on/off -.br -used with ee debug command. used to start or stop php debugging. -.TP -.B --php7=on/off -.br -used with ee debug command. used to start or stop php7 debugging. -.TP -.B --mysql=on/off -.br -used with ee debug command. used to start or stop mysql debugging. -.TP -.B --rewrite=on/off -.br -used with ee debug command. used to start or stop nginx rewrite rules debugging. -.TP -.B --fpm=on/off -.br -used with ee debug command. used to start or stop fpm debugging. -.TP -.B --wp=on/off -.br -used with ee debug command. used to start or stop wordpress site debugging. -.TP -.B --all=on/off -.br -used with ee debug command. used to stop debugging. -.TP -.B --all=off -.br -used with ee debug command. used to stop debugging. -.TP -.B --html -.br -Create a HTML website. -.TP -.B --php -.br -Create a PHP website. -.TP -.B --mysql -.br -Create a PHP+MySQL website. -.TP -.B --wp -.br -Create a WordPress Website. -.TP -.B --wpsubdir -.br -Create a Wordpress Multisite with Sub Directories Setup. -.TP -.B --wpsubdomain -.br -Create a Wordpress Multisite with Sub Domains Setup. -.br -.TP -.B --db -.br -Delete website database. -.br -.TP -.B --files -.br -Delete website webroot. -.br -.TP -.B --no-prompt -.br -Does not prompt for confirmation when delete command used. -.br -.TP -.B --force/-f -.br -Delete website webroot and database forcefully.Remove nginx configuration for site. -.br -.TP -.B --auth -.br -used with ee secure command. Update credential of HTTP authentication -.TP -.B --port -.br -used with ee secure command. Change EasyEngine admin port 22222. -.TP -.B --ip -.br -used with ee secure command. Update whitelist IP address -.SH WORDPRESS CACHING OPTIONS -.TP -.B --w3tc -.br -Install and activate Nginx-helper and W3 Total Cache plugin. -.TP -.B --wpsc -.br -Install and activate Nginx-helper and WP Super Cache plugin. -.TP -.B --wpfc -.br -Install and activate Nginx-helper and W3 Total Cache plugin with -.br -Nginx FastCGI cache. -.TP -.B --wpredis -.br -Install, activate, configure Nginx-helper and Redis Object Cache Plugin, Configure NGINX for Redis Page Caching. -.TP -.B --hhvm -.br -Install, activate Nginx-helper and configure NGINX for HHVM. -.SH FILES -.br -/etc/ee/ee.conf -.SH BUGS -Report bugs at -.SH AUTHOR -.br -.B rtCamp Team -.I \ -.br -.B Mitesh Shah -.I \ -.br -.B Manish -.I \ -.br -.B Gaurav -.I \ -.br -.B Harshad -.I \ -.br -.B Prabuddha -.I \ -.br -.B Shital -.I \ -.br -.B Rajdeep Sharma -.I \ -.br - -.SH "SEE ALSO" -.br -EE: -.I https://rtcamp.com/easyengine/ -.br -FAQ: -.I https://rtcamp.com/easyengine/faq/ -.br -DOCS: -.I https://rtcamp.com/easyengine/docs/ diff --git a/ee/__init__.py b/ee/__init__.py deleted file mode 100644 index de40ea7ca..000000000 --- a/ee/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) diff --git a/ee/cli/bootstrap.py b/ee/cli/bootstrap.py deleted file mode 100644 index a5ce6d75f..000000000 --- a/ee/cli/bootstrap.py +++ /dev/null @@ -1,11 +0,0 @@ -"""EasyEngine bootstrapping.""" - -# All built-in application controllers should be imported, and registered -# in this file in the same way as EEBaseController. - -from cement.core import handler -from ee.cli.controllers.base import EEBaseController - - -def load(app): - handler.register(EEBaseController) diff --git a/ee/cli/controllers/base.py b/ee/cli/controllers/base.py deleted file mode 100644 index 603115a28..000000000 --- a/ee/cli/controllers/base.py +++ /dev/null @@ -1,25 +0,0 @@ -"""EasyEngine base controller.""" - -from cement.core.controller import CementBaseController, expose -from ee.core.variables import EEVariables -VERSION = EEVariables.ee_version - -BANNER = """ -EasyEngine v%s -Copyright (c) 2016 rtCamp Solutions Pvt. Ltd. -""" % VERSION - - -class EEBaseController(CementBaseController): - class Meta: - label = 'base' - description = ("EasyEngine is the commandline tool to manage your" - " websites based on WordPress and Nginx with easy to" - " use commands") - arguments = [ - (['-v', '--version'], dict(action='version', version=BANNER)), - ] - - @expose(hide=True) - def default(self): - self.app.args.print_help() diff --git a/ee/cli/ext/ee_outputhandler.py b/ee/cli/ext/ee_outputhandler.py deleted file mode 100644 index 89e397d46..000000000 --- a/ee/cli/ext/ee_outputhandler.py +++ /dev/null @@ -1,20 +0,0 @@ -# Based on https://github.com/datafolklabs/cement/issues/295 -# To avoid encoding releated error,we defined our custom output handler -# I hope we will remove this when we upgarde to Cement 2.6 (Not released yet) -import os -from cement.utils import fs -from cement.ext.ext_mustache import MustacheOutputHandler - - -class EEOutputHandler(MustacheOutputHandler): - class Meta: - label = 'ee_output_handler' - - def _load_template_from_file(self, path): - for templ_dir in self.app._meta.template_dirs: - full_path = fs.abspath(os.path.join(templ_dir, path)) - if os.path.exists(full_path): - self.app.log.debug('loading template file %s' % full_path) - return open(full_path, encoding='utf-8', mode='r').read() - else: - continue diff --git a/ee/cli/main.py b/ee/cli/main.py deleted file mode 100644 index cd72f0d1e..000000000 --- a/ee/cli/main.py +++ /dev/null @@ -1,135 +0,0 @@ -"""EasyEngine main application entry point.""" -import sys -import os - -# this has to happen after you import sys, but before you import anything -# from Cement "source: https://github.com/datafolklabs/cement/issues/290" -if '--debug' in sys.argv: - sys.argv.remove('--debug') - TOGGLE_DEBUG = True -else: - TOGGLE_DEBUG = False - -from cement.core import foundation -from cement.utils.misc import init_defaults -from cement.core.exc import FrameworkError, CaughtSignal -from cement.ext.ext_argparse import ArgParseArgumentHandler -from ee.core import exc -from ee.cli.ext.ee_outputhandler import EEOutputHandler - -# Application default. Should update config/ee.conf to reflect any -# changes, or additions here. -defaults = init_defaults('ee') - -# All internal/external plugin configurations are loaded from here -defaults['ee']['plugin_config_dir'] = '/etc/ee/plugins.d' - -# External plugins (generally, do not ship with application code) -defaults['ee']['plugin_dir'] = '/var/lib/ee/plugins' - -# External templates (generally, do not ship with application code) -defaults['ee']['template_dir'] = '/var/lib/ee/templates' - - -class EEArgHandler(ArgParseArgumentHandler): - class Meta: - label = 'ee_args_handler' - - def error(self, message): - super(EEArgHandler, self).error("unknown args") - - -class EEApp(foundation.CementApp): - class Meta: - label = 'ee' - - config_defaults = defaults - - # All built-in application bootstrapping (always run) - bootstrap = 'ee.cli.bootstrap' - - # Optional plugin bootstrapping (only run if plugin is enabled) - plugin_bootstrap = 'ee.cli.plugins' - - # Internal templates (ship with application code) - template_module = 'ee.cli.templates' - - # Internal plugins (ship with application code) - plugin_bootstrap = 'ee.cli.plugins' - - extensions = ['mustache'] - - # default output handler - output_handler = EEOutputHandler - - arg_handler = EEArgHandler - - debug = TOGGLE_DEBUG - - -class EETestApp(EEApp): - """A test app that is better suited for testing.""" - class Meta: - argv = [] - config_files = [] - - -# Define the applicaiton object outside of main, as some libraries might wish -# to import it as a global (rather than passing it into another class/func) -app = EEApp() - - -def main(): - try: - global sys - # Default our exit status to 0 (non-error) - code = 0 - - # if not root...kick out - if not os.geteuid() == 0: - print("\nOnly root or sudo user can run this EasyEngine\n") - app.close(1) - - # Setup the application - app.setup() - - # Dump all arguments into ee log - app.log.debug(sys.argv) - - # Run the application - app.run() - except exc.EEError as e: - # Catch our application errors and exit 1 (error) - code = 1 - print(e) - except FrameworkError as e: - # Catch framework errors and exit 1 (error) - code = 1 - print(e) - except CaughtSignal as e: - # Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error) - code = 0 - print(e) - except Exception as e: - code = 1 - print(e) - finally: - # Print an exception (if it occurred) and --debug was passed - if app.debug: - import sys - import traceback - - exc_type, exc_value, exc_traceback = sys.exc_info() - if exc_traceback is not None: - traceback.print_exc() - - # # Close the application - app.close(code) - - -def get_test_app(**kw): - app = EEApp(**kw) - return app - -if __name__ == '__main__': - main() diff --git a/ee/cli/plugins/clean.py b/ee/cli/plugins/clean.py deleted file mode 100644 index 177abe523..000000000 --- a/ee/cli/plugins/clean.py +++ /dev/null @@ -1,108 +0,0 @@ -"""Clean Plugin for EasyEngine.""" - -from ee.core.shellexec import EEShellExec -from ee.core.aptget import EEAptGet -from ee.core.services import EEService -from ee.core.logging import Log -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -import os -import urllib.request - - -def ee_clean_hook(app): - # do something with the ``app`` object here. - pass - - -class EECleanController(CementBaseController): - class Meta: - label = 'clean' - stacked_on = 'base' - stacked_type = 'nested' - description = ('Clean NGINX FastCGI cache, Opcacache, Memcache, Pagespeed Cache, Redis Cache') - arguments = [ - (['--all'], - dict(help='Clean all cache', action='store_true')), - (['--fastcgi'], - dict(help='Clean FastCGI cache', action='store_true')), - (['--memcache'], - dict(help='Clean MemCache', action='store_true')), - (['--opcache'], - dict(help='Clean OpCache', action='store_true')), - (['--redis'], - dict(help='Clean Redis Cache', action='store_true')), - ] - usage = "ee clean [options]" - - @expose(hide=True) - def default(self): - if (not (self.app.pargs.all or self.app.pargs.fastcgi or - self.app.pargs.memcache or self.app.pargs.opcache or - self.app.pargs.redis)): - self.clean_fastcgi() - if self.app.pargs.all: - self.clean_memcache() - self.clean_fastcgi() - self.clean_opcache() - self.clean_redis() - if self.app.pargs.fastcgi: - self.clean_fastcgi() - if self.app.pargs.memcache: - self.clean_memcache() - if self.app.pargs.opcache: - self.clean_opcache() - if self.app.pargs.redis: - self.clean_redis() - @expose(hide=True) - def clean_redis(self): - """This function clears Redis cache""" - if(EEAptGet.is_installed(self, "redis-server")): - Log.info(self, "Cleaning Redis cache") - EEShellExec.cmd_exec(self, "redis-cli flushall") - else: - Log.info(self, "Redis is not installed") - - @expose(hide=True) - def clean_memcache(self): - """This function Clears memcache """ - try: - if(EEAptGet.is_installed(self, "memcached")): - EEService.restart_service(self, "memcached") - Log.info(self, "Cleaning MemCache") - else: - Log.info(self, "Memcache not installed") - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to restart Memcached", False) - - @expose(hide=True) - def clean_fastcgi(self): - """This function clears Fastcgi cache""" - if(os.path.isdir("/var/run/nginx-cache")): - Log.info(self, "Cleaning NGINX FastCGI cache") - EEShellExec.cmd_exec(self, "rm -rf /var/run/nginx-cache/*") - else: - Log.error(self, "Unable to clean FastCGI cache", False) - - @expose(hide=True) - def clean_opcache(self): - """This function clears opcache""" - try: - Log.info(self, "Cleaning opcache") - wp = urllib.request.urlopen(" https://127.0.0.1:22222/cache" - "/opcache/opgui.php?page=reset").read() - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.debug(self, "Unable hit url, " - " https://127.0.0.1:22222/cache/opcache/opgui.php?page=reset," - " please check you have admin tools installed") - Log.debug(self, "please check you have admin tools installed," - " or install them with `ee stack install --admin`") - Log.error(self, "Unable to clean opcache", False) - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EECleanController) - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_clean_hook) diff --git a/ee/cli/plugins/debug.py b/ee/cli/plugins/debug.py deleted file mode 100644 index 57b4d92d8..000000000 --- a/ee/cli/plugins/debug.py +++ /dev/null @@ -1,858 +0,0 @@ -"""Debug Plugin for EasyEngine""" - -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.aptget import EEAptGet -from ee.core.shellexec import * -from ee.core.mysql import EEMysql -from ee.core.services import EEService -from ee.core.logging import Log -from ee.cli.plugins.site_functions import logwatch -from ee.core.variables import EEVariables -from ee.core.fileutils import EEFileUtils -from pynginxconfig import NginxConfig -import os -import configparser -import glob -import signal -import subprocess - - -def ee_debug_hook(app): - # do something with the ``app`` object here. - pass - - -class EEDebugController(CementBaseController): - class Meta: - label = 'debug' - description = 'Used for server level debugging' - stacked_on = 'base' - stacked_type = 'nested' - arguments = [ - (['--stop'], - dict(help='Stop debug', action='store_true')), - (['--start'], - dict(help='Start debug', action='store_true')), - (['--import-slow-log'], - dict(help='Import MySQL slow log to Anemometer database', - action='store_true')), - (['--nginx'], - dict(help='start/stop debugging nginx server ' - 'configuration for site', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--php'], - dict(help='start/stop debugging server php configuration', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--fpm'], - dict(help='start/stop debugging fastcgi configuration', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--php7'], - dict(help='start/stop debugging server php 7.0 configuration', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--fpm7'], - dict(help='start/stop debugging fastcgi 7.0 configuration', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--mysql'], - dict(help='start/stop debugging mysql server', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--wp'], - dict(help='start/stop wordpress debugging for site', - action='store' or 'store_const', choices=('on', 'off'), - const='on', nargs='?')), - (['--rewrite'], - dict(help='start/stop debugging nginx rewrite rules for site', - action='store' or 'store_const', choices=('on', 'off'), - const='on', nargs='?')), - (['--all'], - dict(help='start/stop debugging all server parameters', - action='store' or 'store_const', choices=('on', 'off'), - const='on', nargs='?')), - (['-i', '--interactive'], - dict(help='Interactive debug', action='store_true')), - (['--import-slow-log-interval'], - dict(help='Import MySQL slow log to Anemometer', - action='store', dest='interval')), - (['site_name'], - dict(help='Website Name', nargs='?', default=None)) - ] - usage = "ee debug [] [options] " - - @expose(hide=True) - def debug_nginx(self): - """Start/Stop Nginx debug""" - # start global debug - if (self.app.pargs.nginx == 'on' and not self.app.pargs.site_name): - try: - debug_address = (self.app.config.get('stack', 'ip-address') - .split()) - except Exception as e: - debug_address = ['0.0.0.0/0'] - - # Check if IP address is 127.0.0.1 then enable debug globally - if debug_address == ['127.0.0.1'] or debug_address == []: - debug_address = ['0.0.0.0/0'] - - for ip_addr in debug_address: - if not ("debug_connection "+ip_addr in open('/etc/nginx/' - 'nginx.conf', encoding='utf-8').read()): - Log.info(self, "Setting up Nginx debug connection" - " for "+ip_addr) - EEShellExec.cmd_exec(self, "sed -i \"/events {{/a\\ \\ \\ " - "\\ $(echo debug_connection " - "{ip}\;)\" /etc/nginx/" - "nginx.conf".format(ip=ip_addr)) - self.trigger_nginx = True - - if not self.trigger_nginx: - Log.info(self, "Nginx debug connection already enabled") - - self.msg = self.msg + ["/var/log/nginx/*.error.log"] - - # stop global debug - elif (self.app.pargs.nginx == 'off' and not self.app.pargs.site_name): - if "debug_connection " in open('/etc/nginx/nginx.conf', - encoding='utf-8').read(): - Log.info(self, "Disabling Nginx debug connections") - EEShellExec.cmd_exec(self, "sed -i \"/debug_connection.*/d\"" - " /etc/nginx/nginx.conf") - self.trigger_nginx = True - else: - Log.info(self, "Nginx debug connection already disabled") - - # start site specific debug - elif (self.app.pargs.nginx == 'on' and self.app.pargs.site_name): - config_path = ("/etc/nginx/sites-available/{0}" - .format(self.app.pargs.site_name)) - if os.path.isfile(config_path): - if not EEShellExec.cmd_exec(self, "grep \"error.log debug\" " - "{0}".format(config_path)): - Log.info(self, "Starting NGINX debug connection for " - "{0}".format(self.app.pargs.site_name)) - EEShellExec.cmd_exec(self, "sed -i \"s/error.log;/" - "error.log " - "debug;/\" {0}".format(config_path)) - self.trigger_nginx = True - - else: - Log.info(self, "Nginx debug for site already enabled") - - self.msg = self.msg + ['{0}{1}/logs/error.log' - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - - else: - Log.info(self, "{0} domain not valid" - .format(self.app.pargs.site_name)) - - # stop site specific debug - elif (self.app.pargs.nginx == 'off' and self.app.pargs.site_name): - config_path = ("/etc/nginx/sites-available/{0}" - .format(self.app.pargs.site_name)) - if os.path.isfile(config_path): - if EEShellExec.cmd_exec(self, "grep \"error.log debug\" {0}" - .format(config_path)): - Log.info(self, "Stoping NGINX debug connection for {0}" - .format(self.app.pargs.site_name)) - EEShellExec.cmd_exec(self, "sed -i \"s/error.log debug;/" - "error.log;/\" {0}" - .format(config_path)) - self.trigger_nginx = True - - else: - - Log.info(self, "Nginx debug for site already disabled") - else: - Log.info(self, "{0} domain not valid" - .format(self.app.pargs.site_name)) - - @expose(hide=True) - def debug_php(self): - """Start/Stop PHP debug""" - # PHP global debug start - - if (self.app.pargs.php == 'on' and not self.app.pargs.site_name): - if not (EEShellExec.cmd_exec(self, "sed -n \"/upstream php" - "{/,/}/p \" /etc/nginx/" - "conf.d/upstream.conf " - "| grep 9001")): - - Log.info(self, "Enabling PHP debug") - - # Change upstream.conf - nc = NginxConfig() - nc.loadf('/etc/nginx/conf.d/upstream.conf') - nc.set([('upstream','php',), 'server'], '127.0.0.1:9001') - if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): - nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:9001') - nc.savef('/etc/nginx/conf.d/upstream.conf') - - # Enable xdebug - EEFileUtils.searchreplace(self, "/etc/{0}/mods-available/".format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5") + - "xdebug.ini", - ";zend_extension", - "zend_extension") - - # Fix slow log is not enabled default in PHP5.6 - config = configparser.ConfigParser() - config.read('/etc/{0}/fpm/pool.d/debug.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - config['debug']['slowlog'] = '/var/log/{0}/slow.log'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5") - config['debug']['request_slowlog_timeout'] = '10s' - with open('/etc/{0}/fpm/pool.d/debug.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5"), - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "Writting debug.conf configuration into " - "/etc/{0}/fpm/pool.d/debug.conf".format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - config.write(confifile) - - self.trigger_php = True - self.trigger_nginx = True - else: - Log.info(self, "PHP debug is already enabled") - - self.msg = self.msg + ['/var/log/{0}/slow.log'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")] - - # PHP global debug stop - elif (self.app.pargs.php == 'off' and not self.app.pargs.site_name): - if EEShellExec.cmd_exec(self, " sed -n \"/upstream php {/,/}/p\" " - "/etc/nginx/conf.d/upstream.conf " - "| grep 9001"): - Log.info(self, "Disabling PHP debug") - - # Change upstream.conf - nc = NginxConfig() - nc.loadf('/etc/nginx/conf.d/upstream.conf') - nc.set([('upstream','php',), 'server'], '127.0.0.1:9000') - if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): - nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:8000') - nc.savef('/etc/nginx/conf.d/upstream.conf') - - # Disable xdebug - EEFileUtils.searchreplace(self, "/etc/{0}/mods-available/".format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5") + - "xdebug.ini", - "zend_extension", - ";zend_extension") - - self.trigger_php = True - self.trigger_nginx = True - else: - Log.info(self, "PHP debug is already disabled") - - @expose(hide=True) - def debug_fpm(self): - """Start/Stop PHP5-FPM debug""" - # PHP5-FPM start global debug - if (self.app.pargs.fpm == 'on' and not self.app.pargs.site_name): - if not EEShellExec.cmd_exec(self, "grep \"log_level = debug\" " - "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")): - Log.info(self, "Setting up PHP5-FPM log_level = debug") - config = configparser.ConfigParser() - config.read('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - config.remove_option('global', 'include') - config['global']['log_level'] = 'debug' - config['global']['include'] = '/etc/{0}/fpm/pool.d/*.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5") - with open('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5"), - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "Writting php5-FPM configuration into " - "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - config.write(configfile) - self.trigger_php = True - else: - Log.info(self, "PHP5-FPM log_level = debug already setup") - - self.msg = self.msg + ['/var/log/{0}/fpm.log'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")] - - # PHP5-FPM stop global debug - elif (self.app.pargs.fpm == 'off' and not self.app.pargs.site_name): - if EEShellExec.cmd_exec(self, "grep \"log_level = debug\" " - "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")): - Log.info(self, "Disabling PHP5-FPM log_level = debug") - config = configparser.ConfigParser() - config.read('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - config.remove_option('global', 'include') - config['global']['log_level'] = 'notice' - config['global']['include'] = '/etc/{0}/fpm/pool.d/*.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5") - with open('/etc/{0}/fpm/php-fpm.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5"), - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "writting php5 configuration into " - "/etc/{0}/fpm/php-fpm.conf".format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - config.write(configfile) - - self.trigger_php = True - else: - Log.info(self, "PHP5-FPM log_level = debug already disabled") - - @expose(hide=True) - def debug_php7(self): - """Start/Stop PHP debug""" - # PHP global debug start - - if (self.app.pargs.php7 == 'on' and not self.app.pargs.site_name): - if (EEVariables.ee_platform_codename == 'wheezy' or EEVariables.ee_platform_codename == 'precise'): - Log.error(self,"PHP 7.0 not supported.") - if not (EEShellExec.cmd_exec(self, "sed -n \"/upstream php7" - "{/,/}/p \" /etc/nginx/" - "conf.d/upstream.conf " - "| grep 9170")): - - Log.info(self, "Enabling PHP 7.0 debug") - - # Change upstream.conf - nc = NginxConfig() - nc.loadf('/etc/nginx/conf.d/upstream.conf') - nc.set([('upstream','php7',), 'server'], '127.0.0.1:9170') - if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): - nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:9170') - nc.savef('/etc/nginx/conf.d/upstream.conf') - - # Enable xdebug - EEFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" - "xdebug.ini", - ";zend_extension", - "zend_extension") - - # Fix slow log is not enabled default in PHP5.6 - config = configparser.ConfigParser() - config.read('/etc/php/7.0/fpm/pool.d/debug.conf') - config['debug']['slowlog'] = '/var/log/php/7.0/slow.log' - config['debug']['request_slowlog_timeout'] = '10s' - with open('/etc/php/7.0/fpm/pool.d/debug.conf', - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "Writting debug.conf configuration into " - "/etc/php/7.0/fpm/pool.d/debug.conf") - config.write(confifile) - - self.trigger_php = True - self.trigger_nginx = True - else: - Log.info(self, "PHP debug is already enabled") - - self.msg = self.msg + ['/var/log/php/7.0/slow.log'] - - # PHP global debug stop - elif (self.app.pargs.php7 == 'off' and not self.app.pargs.site_name): - if EEShellExec.cmd_exec(self, " sed -n \"/upstream php7 {/,/}/p\" " - "/etc/nginx/conf.d/upstream.conf " - "| grep 9170"): - Log.info(self, "Disabling PHP 7.0 debug") - - # Change upstream.conf - nc = NginxConfig() - nc.loadf('/etc/nginx/conf.d/upstream.conf') - nc.set([('upstream','php7',), 'server'], '127.0.0.1:9070') - if os.path.isfile("/etc/nginx/common/wpfc-hhvm.conf"): - nc.set([('upstream','hhvm',), 'server'], '127.0.0.1:8000') - nc.savef('/etc/nginx/conf.d/upstream.conf') - - # Disable xdebug - EEFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" - "xdebug.ini", - "zend_extension", - ";zend_extension") - - self.trigger_php = True - self.trigger_nginx = True - else: - Log.info(self, "PHP 7.0 debug is already disabled") - - @expose(hide=True) - def debug_fpm7(self): - """Start/Stop PHP5-FPM debug""" - # PHP5-FPM start global debug - if (self.app.pargs.fpm7 == 'on' and not self.app.pargs.site_name): - if not EEShellExec.cmd_exec(self, "grep \"log_level = debug\" " - "/etc/php/7.0/fpm/php-fpm.conf"): - Log.info(self, "Setting up PHP7.0-FPM log_level = debug") - config = configparser.ConfigParser() - config.read('/etc/php/7.0/fpm/php-fpm.conf') - config.remove_option('global', 'include') - config['global']['log_level'] = 'debug' - config['global']['include'] = '/etc/php/7.0/fpm/pool.d/*.conf' - with open('/etc/php/7.0/fpm/php-fpm.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "Writting php7.0-FPM configuration into " - "/etc/php/7.0/fpm/php-fpm.conf") - config.write(configfile) - self.trigger_php = True - else: - Log.info(self, "PHP7.0-FPM log_level = debug already setup") - - self.msg = self.msg + ['/var/log/php/7.0/fpm.log'] - - # PHP5-FPM stop global debug - elif (self.app.pargs.fpm7 == 'off' and not self.app.pargs.site_name): - if EEShellExec.cmd_exec(self, "grep \"log_level = debug\" " - "/etc/php/7.0/fpm/php-fpm.conf"): - Log.info(self, "Disabling PHP7.0-FPM log_level = debug") - config = configparser.ConfigParser() - config.read('/etc/php/7.0/fpm/php-fpm.conf') - config.remove_option('global', 'include') - config['global']['log_level'] = 'notice' - config['global']['include'] = '/etc/php/7.0/fpm/pool.d/*.conf' - with open('/etc/php/7.0/fpm/php-fpm.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "writting php7.0 configuration into " - "/etc/php/7.0/fpm/php-fpm.conf") - config.write(configfile) - self.trigger_php = True - else: - Log.info(self, "PHP7.0-FPM log_level = debug already disabled") - - @expose(hide=True) - def debug_mysql(self): - """Start/Stop MySQL debug""" - # MySQL start global debug - if (self.app.pargs.mysql == 'on' and not self.app.pargs.site_name): - if not EEShellExec.cmd_exec(self, "mysql -e \"show variables like" - " \'slow_query_log\';\" | " - "grep ON"): - Log.info(self, "Setting up MySQL slow log") - EEMysql.execute(self, "set global slow_query_log = " - "\'ON\';") - EEMysql.execute(self, "set global slow_query_log_file = " - "\'/var/log/mysql/mysql-slow.log\';") - EEMysql.execute(self, "set global long_query_time = 2;") - EEMysql.execute(self, "set global log_queries_not_using" - "_indexes = \'ON\';") - else: - Log.info(self, "MySQL slow log is already enabled") - - self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] - - # MySQL stop global debug - elif (self.app.pargs.mysql == 'off' and not self.app.pargs.site_name): - if EEShellExec.cmd_exec(self, "mysql -e \"show variables like \'" - "slow_query_log\';\" | grep ON"): - Log.info(self, "Disabling MySQL slow log") - EEMysql.execute(self, "set global slow_query_log = \'OFF\';") - EEMysql.execute(self, "set global slow_query_log_file = \'" - "/var/log/mysql/mysql-slow.log\';") - EEMysql.execute(self, "set global long_query_time = 10;") - EEMysql.execute(self, "set global log_queries_not_using_index" - "es = \'OFF\';") - EEShellExec.cmd_exec(self, "crontab -l | sed \'/#EasyEngine " - "start/,/#EasyEngine end/d\' | crontab -") - else: - Log.info(self, "MySQL slow log already disabled") - - @expose(hide=True) - def debug_wp(self): - """Start/Stop WordPress debug""" - if (self.app.pargs.wp == 'on' and self.app.pargs.site_name): - wp_config = ("{0}/{1}/wp-config.php" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)) - webroot = "{0}{1}".format(EEVariables.ee_webroot, - self.app.pargs.site_name) - # Check wp-config.php file into htdocs folder - if not os.path.isfile(wp_config): - wp_config = ("{0}/{1}/htdocs/wp-config.php" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)) - if os.path.isfile(wp_config): - if not EEShellExec.cmd_exec(self, "grep \"\'WP_DEBUG\'\" {0} |" - " grep true".format(wp_config)): - Log.info(self, "Starting WordPress debug") - open("{0}/htdocs/wp-content/debug.log".format(webroot), - encoding='utf-8', mode='a').close() - EEShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/wp-" - "content/debug.log" - "".format(webroot, - EEVariables.ee_php_user)) - EEShellExec.cmd_exec(self, "sed -i \"s/define(\'WP_DEBUG\'" - ".*/define(\'WP_DEBUG\', true);\\n" - "define(\'WP_DEBUG_DISPLAY\', false);" - "\\ndefine(\'WP_DEBUG_LOG\', true);" - "\\ndefine(\'SAVEQUERIES\', true);/\"" - " {0}".format(wp_config)) - EEShellExec.cmd_exec(self, "cd {0}/htdocs/ && wp" - " plugin --allow-root install " - "developer query-monitor" - .format(webroot)) - EEShellExec.cmd_exec(self, "chown -R {1}: {0}/htdocs/" - "wp-content/plugins" - .format(webroot, - EEVariables.ee_php_user)) - - self.msg = self.msg + ['{0}{1}/htdocs/wp-content' - '/debug.log' - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - - else: - Log.info(self, "Unable to find wp-config.php for site: {0}" - .format(self.app.pargs.site_name)) - - elif (self.app.pargs.wp == 'off' and self.app.pargs.site_name): - wp_config = ("{0}{1}/wp-config.php" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)) - webroot = "{0}{1}".format(EEVariables.ee_webroot, - self.app.pargs.site_name) - # Check wp-config.php file into htdocs folder - if not os.path.isfile(wp_config): - wp_config = ("{0}/{1}/htdocs/wp-config.php" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)) - if os.path.isfile(wp_config): - if EEShellExec.cmd_exec(self, "grep \"\'WP_DEBUG\'\" {0} | " - "grep true".format(wp_config)): - Log.info(self, "Disabling WordPress debug") - EEShellExec.cmd_exec(self, "sed -i \"s/define(\'WP_DEBUG\'" - ", true);/define(\'WP_DEBUG\', " - "false);/\" {0}".format(wp_config)) - EEShellExec.cmd_exec(self, "sed -i \"/define(\'" - "WP_DEBUG_DISPLAY\', false);/d\" {0}" - .format(wp_config)) - EEShellExec.cmd_exec(self, "sed -i \"/define(\'" - "WP_DEBUG_LOG\', true);/d\" {0}" - .format(wp_config)) - EEShellExec.cmd_exec(self, "sed -i \"/define(\'" - "SAVEQUERIES\', " - "true);/d\" {0}".format(wp_config)) - else: - Log.info(self, "WordPress debug all already disabled") - else: - Log.error(self, "Missing argument site name") - - @expose(hide=True) - def debug_rewrite(self): - """Start/Stop Nginx rewrite rules debug""" - # Start Nginx rewrite debug globally - if (self.app.pargs.rewrite == 'on' and not self.app.pargs.site_name): - if not EEShellExec.cmd_exec(self, "grep \"rewrite_log on;\" " - "/etc/nginx/nginx.conf"): - Log.info(self, "Setting up Nginx rewrite logs") - EEShellExec.cmd_exec(self, "sed -i \'/http {/a \\\\t" - "rewrite_log on;\' /etc/nginx/nginx.conf") - self.trigger_nginx = True - else: - Log.info(self, "Nginx rewrite logs already enabled") - - if '/var/log/nginx/*.error.log' not in self.msg: - self.msg = self.msg + ['/var/log/nginx/*.error.log'] - - # Stop Nginx rewrite debug globally - elif (self.app.pargs.rewrite == 'off' - and not self.app.pargs.site_name): - if EEShellExec.cmd_exec(self, "grep \"rewrite_log on;\" " - "/etc/nginx/nginx.conf"): - Log.info(self, "Disabling Nginx rewrite logs") - EEShellExec.cmd_exec(self, "sed -i \"/rewrite_log.*/d\"" - " /etc/nginx/nginx.conf") - self.trigger_nginx = True - else: - Log.info(self, "Nginx rewrite logs already disabled") - # Start Nginx rewrite for site - elif (self.app.pargs.rewrite == 'on' and self.app.pargs.site_name): - config_path = ("/etc/nginx/sites-available/{0}" - .format(self.app.pargs.site_name)) - if not EEShellExec.cmd_exec(self, "grep \"rewrite_log on;\" {0}" - .format(config_path)): - Log.info(self, "Setting up Nginx rewrite logs for {0}" - .format(self.app.pargs.site_name)) - EEShellExec.cmd_exec(self, "sed -i \"/access_log/i \\\\\\t" - "rewrite_log on;\" {0}" - .format(config_path)) - self.trigger_nginx = True - else: - Log.info(self, "Nginx rewrite logs for {0} already setup" - .format(self.app.pargs.site_name)) - - if ('{0}{1}/logs/error.log'.format(EEVariables.ee_webroot, - self.app.pargs.site_name) - not in self.msg): - self.msg = self.msg + ['{0}{1}/logs/error.log' - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - - # Stop Nginx rewrite for site - elif (self.app.pargs.rewrite == 'off' and self.app.pargs.site_name): - config_path = ("/etc/nginx/sites-available/{0}" - .format(self.app.pargs.site_name)) - if EEShellExec.cmd_exec(self, "grep \"rewrite_log on;\" {0}" - .format(config_path)): - Log.info(self, "Disabling Nginx rewrite logs for {0}" - .format(self.app.pargs.site_name)) - EEShellExec.cmd_exec(self, "sed -i \"/rewrite_log.*/d\" {0}" - .format(config_path)) - self.trigger_nginx = True - else: - Log.info(self, "Nginx rewrite logs for {0} already " - " disabled".format(self.app.pargs.site_name)) - - @expose(hide=True) - def signal_handler(self, signal, frame): - """Handle Ctrl+c hevent for -i option of debug""" - self.start = False - if self.app.pargs.nginx: - self.app.pargs.nginx = 'off' - self.debug_nginx() - if self.app.pargs.php: - self.app.pargs.php = 'off' - self.debug_php() - if self.app.pargs.php7: - self.app.pargs.php7 = 'off' - self.debug_php7() - if self.app.pargs.fpm: - self.app.pargs.fpm = 'off' - self.debug_fpm() - if self.app.pargs.fpm7: - self.app.pargs.fpm7 = 'off' - self.debug_fpm7() - if self.app.pargs.mysql: - # MySQL debug will not work for remote MySQL - if EEVariables.ee_mysql_host is "localhost": - self.app.pargs.mysql = 'off' - self.debug_mysql() - else: - Log.warn(self, "Remote MySQL found, EasyEngine will not " - "enable remote debug") - if self.app.pargs.wp: - self.app.pargs.wp = 'off' - self.debug_wp() - if self.app.pargs.rewrite: - self.app.pargs.rewrite = 'off' - self.debug_rewrite() - - # Reload Nginx - if self.trigger_nginx: - EEService.reload_service(self, 'nginx') - - # Reload PHP - if self.trigger_php: - if EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic': - if EEAptGet.is_installed(self,'php5.6-fpm'): - EEService.reload_service(self, 'php5.6-fpm') - if EEAptGet.is_installed(self,'php7.0-fpm'): - EEService.reload_service(self, 'php7.0-fpm') - else: - EEService.reload_service(self, 'php5-fpm') - self.app.close(0) - - @expose(hide=True) - def default(self): - """Default function of debug""" - # self.start = True - self.interactive = False - self.msg = [] - self.trigger_nginx = False - self.trigger_php = False - - if ((not self.app.pargs.nginx) and (not self.app.pargs.php) and (not self.app.pargs.php7) - and (not self.app.pargs.fpm) and (not self.app.pargs.fpm7) and (not self.app.pargs.mysql) - and (not self.app.pargs.wp) and (not self.app.pargs.rewrite) - and (not self.app.pargs.all) - and (not self.app.pargs.site_name) - and (not self.app.pargs.import_slow_log) - and (not self.app.pargs.interval)): - if self.app.pargs.stop or self.app.pargs.start: - print("--start/stop option is deprecated since ee3.0.5") - self.app.args.print_help() - else: - self.app.args.print_help() - - if self.app.pargs.import_slow_log: - self.import_slow_log() - - if self.app.pargs.interval: - try: - cron_time = int(self.app.pargs.interval) - except Exception as e: - cron_time = 5 - - try: - if not EEShellExec.cmd_exec(self, "crontab -l | grep " - "'ee debug --import-slow-log'"): - if not cron_time == 0: - Log.info(self, "setting up crontab entry," - " please wait...") - EEShellExec.cmd_exec(self, "/bin/bash -c \"crontab -l " - "2> /dev/null | {{ cat; echo -e" - " \\\"#EasyEngine start MySQL " - "slow log \\n*/{0} * * * * " - "/usr/local/bin/ee debug" - " --import-slow-log\\n" - "#EasyEngine end MySQL slow log" - "\\\"; }} | crontab -\"" - .format(cron_time)) - else: - if not cron_time == 0: - Log.info(self, "updating crontab entry," - " please wait...") - if not EEShellExec.cmd_exec(self, "/bin/bash -c " - "\"crontab " - "-l | sed '/EasyEngine " - "start MySQL slow " - "log/!b;n;c\*\/{0} " - "\* \* \* " - "\* \/usr" - "\/local\/bin\/ee debug " - "--import\-slow\-log' " - "| crontab -\"" - .format(cron_time)): - Log.error(self, "failed to update crontab entry") - else: - Log.info(self, "removing crontab entry," - " please wait...") - if not EEShellExec.cmd_exec(self, "/bin/bash -c " - "\"crontab " - "-l | sed '/EasyEngine " - "start MySQL slow " - "log/,+2d'" - "| crontab -\"" - .format(cron_time)): - Log.error(self, "failed to remove crontab entry") - except CommandExecutionError as e: - Log.debug(self, str(e)) - - if self.app.pargs.all == 'on': - if self.app.pargs.site_name: - self.app.pargs.wp = 'on' - self.app.pargs.nginx = 'on' - self.app.pargs.php = 'on' - self.app.pargs.fpm = 'on' - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') and EEAptGet.is_installed(self,'php7.0-fpm'): - self.app.pargs.php7 = 'on' - self.app.pargs.fpm7 = 'on' - self.app.pargs.mysql = 'on' - self.app.pargs.rewrite = 'on' - - if self.app.pargs.all == 'off': - if self.app.pargs.site_name: - self.app.pargs.wp = 'off' - self.app.pargs.nginx = 'off' - self.app.pargs.php = 'off' - self.app.pargs.fpm = 'off' - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') and EEAptGet.is_installed(self,'php7.0-fpm'): - self.app.pargs.php7 = 'off' - self.app.pargs.fpm7 = 'off' - self.app.pargs.mysql = 'off' - self.app.pargs.rewrite = 'off' - - if ((not self.app.pargs.nginx) and (not self.app.pargs.php) and (not self.app.pargs.php7) - and (not self.app.pargs.fpm) and (not self.app.pargs.fpm7) and (not self.app.pargs.mysql) - and (not self.app.pargs.wp) and (not self.app.pargs.rewrite) - and self.app.pargs.site_name): - self.app.args.print_help() - # self.app.pargs.nginx = 'on' - # self.app.pargs.wp = 'on' - # self.app.pargs.rewrite = 'on' - - if self.app.pargs.nginx: - self.debug_nginx() - if self.app.pargs.php: - self.debug_php() - if self.app.pargs.fpm: - self.debug_fpm() - if self.app.pargs.php7: - self.debug_php7() - if self.app.pargs.fpm7: - self.debug_fpm7() - if self.app.pargs.mysql: - # MySQL debug will not work for remote MySQL - if EEVariables.ee_mysql_host is "localhost": - self.debug_mysql() - else: - Log.warn(self, "Remote MySQL found, EasyEngine will not " - "enable remote debug") - if self.app.pargs.wp: - self.debug_wp() - if self.app.pargs.rewrite: - self.debug_rewrite() - - if self.app.pargs.interactive: - self.interactive = True - - # Reload Nginx - if self.trigger_nginx: - EEService.reload_service(self, 'nginx') - # Reload PHP - if self.trigger_php: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if EEAptGet.is_installed(self,'php5.6-fpm'): - EEService.restart_service(self, 'php5.6-fpm') - if EEAptGet.is_installed(self,'php7.0-fpm'): - EEService.restart_service(self, 'php7.0-fpm') - else: - EEService.restart_service(self, 'php5-fpm') - if EEVariables.ee_platform_codename == 'jessie': - EEService.restart_service(self, 'php7.0-fpm') - - if len(self.msg) > 0: - if not self.app.pargs.interactive: - disp_msg = ' '.join(self.msg) - Log.info(self, "Use following command to check debug logs:\n" - + Log.ENDC + "tail -f {0}".format(disp_msg)) - else: - signal.signal(signal.SIGINT, self.signal_handler) - watch_list = [] - for w_list in self.msg: - watch_list = watch_list + glob.glob(w_list) - - logwatch(self, watch_list) - - @expose(hide=True) - def import_slow_log(self): - """Default function for import slow log""" - if os.path.isdir("{0}22222/htdocs/db/anemometer" - .format(EEVariables.ee_webroot)): - if os.path.isfile("/var/log/mysql/mysql-slow.log"): - # Get Anemometer user name and password - Log.info(self, "Importing MySQL slow log to Anemometer") - host = os.popen("grep -e \"\'host\'\" {0}22222/htdocs/" - .format(EEVariables.ee_webroot) - + "db/anemometer/conf/config.inc.php " - "| head -1 | cut -d\\\' -f4 | " - "tr -d '\n'").read() - user = os.popen("grep -e \"\'user\'\" {0}22222/htdocs/" - .format(EEVariables.ee_webroot) - + "db/anemometer/conf/config.inc.php " - "| head -1 | cut -d\\\' -f4 | " - "tr -d '\n'").read() - password = os.popen("grep -e \"\'password\'\" {0}22222/" - .format(EEVariables.ee_webroot) - + "htdocs/db/anemometer/conf" - "/config.inc.php " - "| head -1 | cut -d\\\' -f4 | " - "tr -d '\n'").read() - - # Import slow log Anemometer using pt-query-digest - try: - EEShellExec.cmd_exec(self, "pt-query-digest --user={0} " - "--password={1} " - "--review D=slow_query_log," - "t=global_query_review " - "--history D=slow_query_log,t=" - "global_query_review_history " - "--no-report --limit=0% " - "--filter=\" \\$event->{{Bytes}} = " - "length(\\$event->{{arg}}) " - "and \\$event->{{hostname}}=\\\"" - "{2}\\\"\" " - "/var/log/mysql/mysql-slow.log" - .format(user, password, host)) - except CommandExecutionError as e: - Log.debug(self, str(e)) - Log.error(self, "MySQL slow log import failed.") - else: - Log.error(self, "MySQL slow log file not found," - " so not imported slow logs") - else: - Log.error(self, "Anemometer is not installed." + - Log.ENDC + "\nYou can install Anemometer with " - "this command " - + Log.BOLD + "\n `ee stack install --utils`" - + Log.ENDC) - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EEDebugController) - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_debug_hook) diff --git a/ee/cli/plugins/import_slow_log.py b/ee/cli/plugins/import_slow_log.py deleted file mode 100644 index ea035aee0..000000000 --- a/ee/cli/plugins/import_slow_log.py +++ /dev/null @@ -1,34 +0,0 @@ -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.shellexec import EEShellExec -from ee.core.logging import Log -from ee.core.variables import EEVariables -import os - - -def ee_import_slow_log_hook(app): - pass - - -class EEImportslowlogController(CementBaseController): - class Meta: - label = 'import_slow_log' - stacked_on = 'base' - stacked_type = 'nested' - description = 'Import MySQL slow log to Anemometer database' - usage = "ee import-slow-log" - - @expose(hide=True) - def default(self): - Log.info(self, "This command is deprecated." - " You can use this command instead, " + - Log.ENDC + Log.BOLD + "\n`ee debug --import-slow-log`" + - Log.ENDC) - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EEImportslowlogController) - - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_import_slow_log_hook) diff --git a/ee/cli/plugins/info.py b/ee/cli/plugins/info.py deleted file mode 100644 index be2446731..000000000 --- a/ee/cli/plugins/info.py +++ /dev/null @@ -1,290 +0,0 @@ -"""EEInfo Plugin for EasyEngine.""" - -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.variables import EEVariables -from pynginxconfig import NginxConfig -from ee.core.aptget import EEAptGet -from ee.core.shellexec import EEShellExec -from ee.core.logging import Log -import os -import configparser - - -def ee_info_hook(app): - # do something with the ``app`` object here. - pass - - -class EEInfoController(CementBaseController): - class Meta: - label = 'info' - stacked_on = 'base' - stacked_type = 'nested' - description = ('Display configuration information related to Nginx,' - ' PHP and MySQL') - arguments = [ - (['--mysql'], - dict(help='Get MySQL configuration information', - action='store_true')), - (['--php'], - dict(help='Get PHP configuration information', - action='store_true')), - (['--php7'], - dict(help='Get PHP 7.0 configuration information', - action='store_true')), - (['--nginx'], - dict(help='Get Nginx configuration information', - action='store_true')), - ] - usage = "ee info [options]" - - @expose(hide=True) - def info_nginx(self): - """Display Nginx information""" - version = os.popen("nginx -v 2>&1 | cut -d':' -f2 | cut -d' ' -f2 | " - "cut -d'/' -f2 | tr -d '\n'").read() - allow = os.popen("grep ^allow /etc/nginx/common/acl.conf | " - "cut -d' ' -f2 | cut -d';' -f1 | tr '\n' ' '").read() - nc = NginxConfig() - nc.loadf('/etc/nginx/nginx.conf') - user = nc.get('user')[1] - worker_processes = nc.get('worker_processes')[1] - worker_connections = nc.get([('events',), 'worker_connections'])[1] - keepalive_timeout = nc.get([('http',), 'keepalive_timeout'])[1] - fastcgi_read_timeout = nc.get([('http',), - 'fastcgi_read_timeout'])[1] - client_max_body_size = nc.get([('http',), - 'client_max_body_size'])[1] - data = dict(version=version, allow=allow, user=user, - worker_processes=worker_processes, - keepalive_timeout=keepalive_timeout, - worker_connections=worker_connections, - fastcgi_read_timeout=fastcgi_read_timeout, - client_max_body_size=client_max_body_size) - self.app.render((data), 'info_nginx.mustache') - - @expose(hide=True) - def info_php(self): - """Display PHP information""" - version = os.popen("{0} -v 2>/dev/null | head -n1 | cut -d' ' -f2 |".format("php5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php") + - " cut -d'+' -f1 | tr -d '\n'").read - config = configparser.ConfigParser() - config.read('/etc/{0}/fpm/php.ini'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - expose_php = config['PHP']['expose_php'] - memory_limit = config['PHP']['memory_limit'] - post_max_size = config['PHP']['post_max_size'] - upload_max_filesize = config['PHP']['upload_max_filesize'] - max_execution_time = config['PHP']['max_execution_time'] - - config.read('/etc/{0}/fpm/pool.d/www.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - www_listen = config['www']['listen'] - www_ping_path = config['www']['ping.path'] - www_pm_status_path = config['www']['pm.status_path'] - www_pm = config['www']['pm'] - www_pm_max_requests = config['www']['pm.max_requests'] - www_pm_max_children = config['www']['pm.max_children'] - www_pm_start_servers = config['www']['pm.start_servers'] - www_pm_min_spare_servers = config['www']['pm.min_spare_servers'] - www_pm_max_spare_servers = config['www']['pm.max_spare_servers'] - www_request_terminate_time = (config['www'] - ['request_terminate_timeout']) - try: - www_xdebug = (config['www']['php_admin_flag[xdebug.profiler_enable' - '_trigger]']) - except Exception as e: - www_xdebug = 'off' - - config.read('/etc/{0}/fpm/pool.d/debug.conf'.format("php/5.6" if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') else "php5")) - debug_listen = config['debug']['listen'] - debug_ping_path = config['debug']['ping.path'] - debug_pm_status_path = config['debug']['pm.status_path'] - debug_pm = config['debug']['pm'] - debug_pm_max_requests = config['debug']['pm.max_requests'] - debug_pm_max_children = config['debug']['pm.max_children'] - debug_pm_start_servers = config['debug']['pm.start_servers'] - debug_pm_min_spare_servers = config['debug']['pm.min_spare_servers'] - debug_pm_max_spare_servers = config['debug']['pm.max_spare_servers'] - debug_request_terminate = (config['debug'] - ['request_terminate_timeout']) - try: - debug_xdebug = (config['debug']['php_admin_flag[xdebug.profiler_' - 'enable_trigger]']) - except Exception as e: - debug_xdebug = 'off' - - data = dict(version=version, expose_php=expose_php, - memory_limit=memory_limit, post_max_size=post_max_size, - upload_max_filesize=upload_max_filesize, - max_execution_time=max_execution_time, - www_listen=www_listen, www_ping_path=www_ping_path, - www_pm_status_path=www_pm_status_path, www_pm=www_pm, - www_pm_max_requests=www_pm_max_requests, - www_pm_max_children=www_pm_max_children, - www_pm_start_servers=www_pm_start_servers, - www_pm_min_spare_servers=www_pm_min_spare_servers, - www_pm_max_spare_servers=www_pm_max_spare_servers, - www_request_terminate_timeout=www_request_terminate_time, - www_xdebug_profiler_enable_trigger=www_xdebug, - debug_listen=debug_listen, debug_ping_path=debug_ping_path, - debug_pm_status_path=debug_pm_status_path, - debug_pm=debug_pm, - debug_pm_max_requests=debug_pm_max_requests, - debug_pm_max_children=debug_pm_max_children, - debug_pm_start_servers=debug_pm_start_servers, - debug_pm_min_spare_servers=debug_pm_min_spare_servers, - debug_pm_max_spare_servers=debug_pm_max_spare_servers, - debug_request_terminate_timeout=debug_request_terminate, - debug_xdebug_profiler_enable_trigger=debug_xdebug) - self.app.render((data), 'info_php.mustache') - - @expose(hide=True) - def info_php7(self): - """Display PHP information""" - version = os.popen("php7.0 -v 2>/dev/null | head -n1 | cut -d' ' -f2 |" - " cut -d'+' -f1 | tr -d '\n'").read - config = configparser.ConfigParser() - config.read('/etc/php/7.0/fpm/php.ini') - expose_php = config['PHP']['expose_php'] - memory_limit = config['PHP']['memory_limit'] - post_max_size = config['PHP']['post_max_size'] - upload_max_filesize = config['PHP']['upload_max_filesize'] - max_execution_time = config['PHP']['max_execution_time'] - - config.read('/etc/php/7.0/fpm/pool.d/www.conf') - www_listen = config['www']['listen'] - www_ping_path = config['www']['ping.path'] - www_pm_status_path = config['www']['pm.status_path'] - www_pm = config['www']['pm'] - www_pm_max_requests = config['www']['pm.max_requests'] - www_pm_max_children = config['www']['pm.max_children'] - www_pm_start_servers = config['www']['pm.start_servers'] - www_pm_min_spare_servers = config['www']['pm.min_spare_servers'] - www_pm_max_spare_servers = config['www']['pm.max_spare_servers'] - www_request_terminate_time = (config['www'] - ['request_terminate_timeout']) - try: - www_xdebug = (config['www']['php_admin_flag[xdebug.profiler_enable' - '_trigger]']) - except Exception as e: - www_xdebug = 'off' - - config.read('/etc/php/7.0/fpm/pool.d/debug.conf') - debug_listen = config['debug']['listen'] - debug_ping_path = config['debug']['ping.path'] - debug_pm_status_path = config['debug']['pm.status_path'] - debug_pm = config['debug']['pm'] - debug_pm_max_requests = config['debug']['pm.max_requests'] - debug_pm_max_children = config['debug']['pm.max_children'] - debug_pm_start_servers = config['debug']['pm.start_servers'] - debug_pm_min_spare_servers = config['debug']['pm.min_spare_servers'] - debug_pm_max_spare_servers = config['debug']['pm.max_spare_servers'] - debug_request_terminate = (config['debug'] - ['request_terminate_timeout']) - try: - debug_xdebug = (config['debug']['php_admin_flag[xdebug.profiler_' - 'enable_trigger]']) - except Exception as e: - debug_xdebug = 'off' - - data = dict(version=version, expose_php=expose_php, - memory_limit=memory_limit, post_max_size=post_max_size, - upload_max_filesize=upload_max_filesize, - max_execution_time=max_execution_time, - www_listen=www_listen, www_ping_path=www_ping_path, - www_pm_status_path=www_pm_status_path, www_pm=www_pm, - www_pm_max_requests=www_pm_max_requests, - www_pm_max_children=www_pm_max_children, - www_pm_start_servers=www_pm_start_servers, - www_pm_min_spare_servers=www_pm_min_spare_servers, - www_pm_max_spare_servers=www_pm_max_spare_servers, - www_request_terminate_timeout=www_request_terminate_time, - www_xdebug_profiler_enable_trigger=www_xdebug, - debug_listen=debug_listen, debug_ping_path=debug_ping_path, - debug_pm_status_path=debug_pm_status_path, - debug_pm=debug_pm, - debug_pm_max_requests=debug_pm_max_requests, - debug_pm_max_children=debug_pm_max_children, - debug_pm_start_servers=debug_pm_start_servers, - debug_pm_min_spare_servers=debug_pm_min_spare_servers, - debug_pm_max_spare_servers=debug_pm_max_spare_servers, - debug_request_terminate_timeout=debug_request_terminate, - debug_xdebug_profiler_enable_trigger=debug_xdebug) - self.app.render((data), 'info_php.mustache') - - @expose(hide=True) - def info_mysql(self): - """Display MySQL information""" - version = os.popen("mysql -V | awk '{print($5)}' | cut -d ',' " - "-f1 | tr -d '\n'").read() - host = "localhost" - port = os.popen("mysql -e \"show variables\" | grep ^port | awk " - "'{print($2)}' | tr -d '\n'").read() - wait_timeout = os.popen("mysql -e \"show variables\" | grep " - "^wait_timeout | awk '{print($2)}' | " - "tr -d '\n'").read() - interactive_timeout = os.popen("mysql -e \"show variables\" | grep " - "^interactive_timeout | awk " - "'{print($2)}' | tr -d '\n'").read() - max_used_connections = os.popen("mysql -e \"show global status\" | " - "grep Max_used_connections | awk " - "'{print($2)}' | tr -d '\n'").read() - datadir = os.popen("mysql -e \"show variables\" | grep datadir | awk" - " '{print($2)}' | tr -d '\n'").read() - socket = os.popen("mysql -e \"show variables\" | grep \"^socket\" | " - "awk '{print($2)}' | tr -d '\n'").read() - data = dict(version=version, host=host, port=port, - wait_timeout=wait_timeout, - interactive_timeout=interactive_timeout, - max_used_connections=max_used_connections, - datadir=datadir, socket=socket) - self.app.render((data), 'info_mysql.mustache') - - @expose(hide=True) - def default(self): - """default function for info""" - if (not self.app.pargs.nginx and not self.app.pargs.php - and not self.app.pargs.mysql and not self.app.pargs.php7): - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - if EEAptGet.is_installed(self, 'php7.0-fpm'): - self.app.pargs.php = True - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom') or EEAptGet.is_installed(self, 'nginx-common'): - self.info_nginx() - else: - Log.error(self, "Nginx is not installed") - - if self.app.pargs.php: - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if EEAptGet.is_installed(self, 'php5-fpm'): - self.info_php() - else: - Log.error(self, "PHP5 is not installed") - else: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - self.info_php() - else: - Log.error(self, "PHP5.6 is not installed") - - if self.app.pargs.php7: - if EEAptGet.is_installed(self, 'php7.0-fpm'): - self.info_php7() - else: - Log.error(self, "PHP 7.0 is not installed") - - if self.app.pargs.mysql: - if EEShellExec.cmd_exec(self, "mysqladmin ping"): - self.info_mysql() - else: - Log.error(self, "MySQL is not installed") - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EEInfoController) - - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_info_hook) diff --git a/ee/cli/plugins/log.py b/ee/cli/plugins/log.py deleted file mode 100644 index fbaffd94b..000000000 --- a/ee/cli/plugins/log.py +++ /dev/null @@ -1,578 +0,0 @@ -"""Debug Plugin for EasyEngine""" - -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.logging import Log -from ee.cli.plugins.site_functions import logwatch -from ee.core.variables import EEVariables -from ee.core.fileutils import EEFileUtils -from ee.core.shellexec import EEShellExec -from ee.core.sendmail import EESendMail -from ee.core.mysql import EEMysql -import os -import glob -import gzip - - -def ee_log_hook(app): - # do something with the ``app`` object here. - pass - - -class EELogController(CementBaseController): - class Meta: - label = 'log' - description = 'Perform operations on Nginx, PHP, MySQL log file' - stacked_on = 'base' - stacked_type = 'nested' - usage = "ee log [] [options]" - - @expose(hide=True) - def default(self): - self.app.args.print_help() - - -class EELogShowController(CementBaseController): - class Meta: - label = 'show' - description = 'Show Nginx, PHP, MySQL log file' - stacked_on = 'log' - stacked_type = 'nested' - arguments = [ - (['--all'], - dict(help='Show All logs file', action='store_true')), - (['--nginx'], - dict(help='Show Nginx Error logs file', action='store_true')), - (['--php'], - dict(help='Show PHP Error logs file', action='store_true')), - (['--fpm'], - dict(help='Show PHP5-fpm slow logs file', - action='store_true')), - (['--mysql'], - dict(help='Show MySQL logs file', action='store_true')), - (['--wp'], - dict(help='Show Site specific WordPress logs file', - action='store_true')), - (['--access'], - dict(help='Show Nginx access log file', - action='store_true')), - (['site_name'], - dict(help='Website Name', nargs='?', default=None)) - ] - usage = "ee log show [] [options]" - - @expose(hide=True) - def default(self): - """Default function of log show""" - self.msg = [] - - if self.app.pargs.php: - self.app.pargs.nginx = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (not self.app.pargs.site_name)): - self.app.pargs.nginx = True - self.app.pargs.fpm = True - self.app.pargs.mysql = True - self.app.pargs.access = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (self.app.pargs.site_name)): - self.app.pargs.nginx = True - self.app.pargs.wp = True - self.app.pargs.access = True - self.app.pargs.mysql = True - - if self.app.pargs.nginx and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*error.log"] - - if self.app.pargs.access and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*access.log"] - - if self.app.pargs.fpm: - open('/var/log/php5/slow.log', 'a').close() - open('/var/log/php5/fpm.log', 'a').close() - self.msg = self.msg + ['/var/log/php5/slow.log', - '/var/log/php5/fpm.log'] - if self.app.pargs.mysql: - # MySQL debug will not work for remote MySQL - if EEVariables.ee_mysql_host is "localhost": - if os.path.isfile('/var/log/mysql/mysql-slow.log'): - self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] - else: - Log.info(self, "MySQL slow-log not found, skipped") - else: - Log.warn(self, "Remote MySQL found, EasyEngine is not able to" - "show MySQL log file") - - if self.app.pargs.site_name: - webroot = "{0}{1}".format(EEVariables.ee_webroot, - self.app.pargs.site_name) - - if not os.path.isdir(webroot): - Log.error(self, "Site not present, quitting") - if self.app.pargs.access: - self.msg = self.msg + ["{0}/{1}/logs/access.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.nginx: - self.msg = self.msg + ["{0}/{1}/logs/error.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.wp: - if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): - if not os.path.isfile('{0}/logs/debug.log' - .format(webroot)): - if not os.path.isfile('{0}/htdocs/wp-content/debug.log' - .format(webroot)): - open("{0}/htdocs/wp-content/debug.log" - .format(webroot), - encoding='utf-8', mode='a').close() - EEShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" - "wp-content/debug.log" - "".format(webroot, - EEVariables - .ee_php_user) - ) - # create symbolic link for debug log - EEFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" - "debug.log" - .format(webroot), - '{0}/logs/debug.log' - .format(webroot)]) - - self.msg = self.msg + ["{0}/{1}/logs/debug.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - else: - Log.info(self, "Site is not WordPress site, skipping " - "WordPress logs") - - watch_list = [] - for w_list in self.msg: - watch_list = watch_list + glob.glob(w_list) - - logwatch(self, watch_list) - - -class EELogResetController(CementBaseController): - class Meta: - label = 'reset' - description = 'Reset Nginx, PHP, MySQL log file' - stacked_on = 'log' - stacked_type = 'nested' - arguments = [ - (['--all'], - dict(help='Reset All logs file', action='store_true')), - (['--nginx'], - dict(help='Reset Nginx Error logs file', action='store_true')), - (['--php'], - dict(help='Reset PHP Error logs file', action='store_true')), - (['--fpm'], - dict(help='Reset PHP5-fpm slow logs file', - action='store_true')), - (['--mysql'], - dict(help='Reset MySQL logs file', action='store_true')), - (['--wp'], - dict(help='Reset Site specific WordPress logs file', - action='store_true')), - (['--access'], - dict(help='Reset Nginx access log file', - action='store_true')), - (['--slow-log-db'], - dict(help='Drop all rows from slowlog table in database', - action='store_true')), - (['site_name'], - dict(help='Website Name', nargs='?', default=None)) - ] - usage = "ee log reset [] [options]" - - @expose(hide=True) - def default(self): - """Default function of log reset""" - self.msg = [] - - if self.app.pargs.php: - self.app.pargs.nginx = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (not self.app.pargs.site_name) - and (not self.app.pargs.slow_log_db)): - self.app.pargs.nginx = True - self.app.pargs.fpm = True - self.app.pargs.mysql = True - self.app.pargs.access = True - self.app.pargs.slow_log_db = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (self.app.pargs.site_name) - and (not self.app.pargs.slow-log-db)): - self.app.pargs.nginx = True - self.app.pargs.wp = True - self.app.pargs.access = True - self.app.pargs.mysql = True - - if self.app.pargs.slow_log_db: - if os.path.isdir("/var/www/22222/htdocs/db/anemometer"): - Log.info(self, "Resetting MySQL slow_query_log database table") - EEMysql.execute(self, "TRUNCATE TABLE " - "slow_query_log.global_query_review_history") - EEMysql.execute(self, "TRUNCATE TABLE " - "slow_query_log.global_query_review") - - if self.app.pargs.nginx and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*error.log"] - - if self.app.pargs.access and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*access.log"] - - if self.app.pargs.fpm: - open('/var/log/php5/slow.log', 'a').close() - open('/var/log/php5/fpm.log', 'a').close() - self.msg = self.msg + ['/var/log/php5/slow.log', - '/var/log/php5/fpm.log'] - if self.app.pargs.mysql: - # MySQL debug will not work for remote MySQL - if EEVariables.ee_mysql_host is "localhost": - if os.path.isfile('/var/log/mysql/mysql-slow.log'): - self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] - else: - Log.info(self, "MySQL slow-log not found, skipped") - else: - Log.warn(self, "Remote MySQL found, EasyEngine is not able to" - "show MySQL log file") - - if self.app.pargs.site_name: - webroot = "{0}{1}".format(EEVariables.ee_webroot, - self.app.pargs.site_name) - - if not os.path.isdir(webroot): - Log.error(self, "Site not present, quitting") - if self.app.pargs.access: - self.msg = self.msg + ["{0}/{1}/logs/access.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.nginx: - self.msg = self.msg + ["{0}/{1}/logs/error.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.wp: - if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): - if not os.path.isfile('{0}/logs/debug.log' - .format(webroot)): - if not os.path.isfile('{0}/htdocs/wp-content/debug.log' - .format(webroot)): - open("{0}/htdocs/wp-content/debug.log" - .format(webroot), - encoding='utf-8', mode='a').close() - EEShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" - "wp-content/debug.log" - "".format(webroot, - EEVariables - .ee_php_user) - ) - # create symbolic link for debug log - EEFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" - "debug.log" - .format(webroot), - '{0}/logs/debug.log' - .format(webroot)]) - - self.msg = self.msg + ["{0}/{1}/logs/debug.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - else: - Log.info(self, "Site is not WordPress site, skipping " - "WordPress logs") - - reset_list = [] - for r_list in self.msg: - reset_list = reset_list + glob.glob(r_list) - - # Clearing content of file - for r_list in reset_list: - Log.info(self, "Resetting file {file}".format(file=r_list)) - open(r_list, 'w').close() - - -class EELogGzipController(CementBaseController): - class Meta: - label = 'gzip' - description = 'GZip Nginx, PHP, MySQL log file' - stacked_on = 'log' - stacked_type = 'nested' - arguments = [ - (['--all'], - dict(help='GZip All logs file', action='store_true')), - (['--nginx'], - dict(help='GZip Nginx Error logs file', action='store_true')), - (['--php'], - dict(help='GZip PHP Error logs file', action='store_true')), - (['--fpm'], - dict(help='GZip PHP5-fpm slow logs file', - action='store_true')), - (['--mysql'], - dict(help='GZip MySQL logs file', action='store_true')), - (['--wp'], - dict(help='GZip Site specific WordPress logs file', - action='store_true')), - (['--access'], - dict(help='GZip Nginx access log file', - action='store_true')), - (['site_name'], - dict(help='Website Name', nargs='?', default=None)) - ] - usage = "ee log gzip [] [options]" - - @expose(hide=True) - def default(self): - """Default function of log GZip""" - self.msg = [] - - if self.app.pargs.php: - self.app.pargs.nginx = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (not self.app.pargs.site_name)): - self.app.pargs.nginx = True - self.app.pargs.fpm = True - self.app.pargs.mysql = True - self.app.pargs.access = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (self.app.pargs.site_name)): - self.app.pargs.nginx = True - self.app.pargs.wp = True - self.app.pargs.access = True - self.app.pargs.mysql = True - - if self.app.pargs.nginx and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*error.log"] - - if self.app.pargs.access and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*access.log"] - - if self.app.pargs.fpm: - open('/var/log/php5/slow.log', 'a').close() - open('/var/log/php5/fpm.log', 'a').close() - self.msg = self.msg + ['/var/log/php5/slow.log', - '/var/log/php5/fpm.log'] - if self.app.pargs.mysql: - # MySQL debug will not work for remote MySQL - if EEVariables.ee_mysql_host is "localhost": - if os.path.isfile('/var/log/mysql/mysql-slow.log'): - self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] - else: - Log.info(self, "MySQL slow-log not found, skipped") - - else: - Log.warn(self, "Remote MySQL found, EasyEngine is not able to" - "show MySQL log file") - - if self.app.pargs.site_name: - webroot = "{0}{1}".format(EEVariables.ee_webroot, - self.app.pargs.site_name) - - if not os.path.isdir(webroot): - Log.error(self, "Site not present, quitting") - if self.app.pargs.access: - self.msg = self.msg + ["{0}/{1}/logs/access.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.nginx: - self.msg = self.msg + ["{0}/{1}/logs/error.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.wp: - if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): - if not os.path.isfile('{0}/logs/debug.log' - .format(webroot)): - if not os.path.isfile('{0}/htdocs/wp-content/debug.log' - .format(webroot)): - open("{0}/htdocs/wp-content/debug.log" - .format(webroot), - encoding='utf-8', mode='a').close() - EEShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" - "wp-content/debug.log" - "".format(webroot, - EEVariables - .ee_php_user) - ) - # create symbolic link for debug log - EEFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" - "debug.log" - .format(webroot), - '{0}/logs/debug.log' - .format(webroot)]) - - self.msg = self.msg + ["{0}/{1}/logs/debug.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - else: - Log.info(self, "Site is not WordPress site, skipping " - "WordPress logs") - - gzip_list = [] - for g_list in self.msg: - gzip_list = gzip_list + glob.glob(g_list) - - # Gzip content of file - for g_list in gzip_list: - Log.info(self, "Gzipping file {file}".format(file=g_list)) - in_file = g_list - in_data = open(in_file, "rb").read() - out_gz = g_list + ".gz" - gzf = gzip.open(out_gz, "wb") - gzf.write(in_data) - gzf.close() - - -class EELogMailController(CementBaseController): - class Meta: - label = 'mail' - description = 'Mail Nginx, PHP, MySQL log file' - stacked_on = 'log' - stacked_type = 'nested' - arguments = [ - (['--all'], - dict(help='Mail All logs file', action='store_true')), - (['--nginx'], - dict(help='Mail Nginx Error logs file', action='store_true')), - (['--php'], - dict(help='Mail PHP Error logs file', action='store_true')), - (['--fpm'], - dict(help='Mail PHP5-fpm slow logs file', - action='store_true')), - (['--mysql'], - dict(help='Mail MySQL logs file', action='store_true')), - (['--wp'], - dict(help='Mail Site specific WordPress logs file', - action='store_true')), - (['--access'], - dict(help='Mail Nginx access log file', - action='store_true')), - (['site_name'], - dict(help='Website Name', nargs='?', default=None)), - (['--to'], - dict(help='EMail addresses to send log files', action='append', - dest='to', nargs=1, required=True)), - ] - usage = "ee log mail [] [options]" - - @expose(hide=True) - def default(self): - """Default function of log Mail""" - self.msg = [] - - if self.app.pargs.php: - self.app.pargs.nginx = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (not self.app.pargs.site_name)): - self.app.pargs.nginx = True - self.app.pargs.fpm = True - self.app.pargs.mysql = True - self.app.pargs.access = True - - if ((not self.app.pargs.nginx) and (not self.app.pargs.fpm) - and (not self.app.pargs.mysql) and (not self.app.pargs.access) - and (not self.app.pargs.wp) and (self.app.pargs.site_name)): - self.app.pargs.nginx = True - self.app.pargs.wp = True - self.app.pargs.access = True - self.app.pargs.mysql = True - - if self.app.pargs.nginx and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*error.log"] - - if self.app.pargs.access and (not self.app.pargs.site_name): - self.msg = self.msg + ["/var/log/nginx/*access.log"] - - if self.app.pargs.fpm: - open('/var/log/php5/slow.log', 'a').close() - open('/var/log/php5/fpm.log', 'a').close() - self.msg = self.msg + ['/var/log/php5/slow.log', - '/var/log/php5/fpm.log'] - if self.app.pargs.mysql: - # MySQL debug will not work for remote MySQL - if EEVariables.ee_mysql_host is "localhost": - if os.path.isfile('/var/log/mysql/mysql-slow.log'): - self.msg = self.msg + ['/var/log/mysql/mysql-slow.log'] - else: - Log.info(self, "MySQL slow-log not found, skipped") - else: - Log.warn(self, "Remote MySQL found, EasyEngine is not able to" - "show MySQL log file") - - if self.app.pargs.site_name: - webroot = "{0}{1}".format(EEVariables.ee_webroot, - self.app.pargs.site_name) - - if not os.path.isdir(webroot): - Log.error(self, "Site not present, quitting") - if self.app.pargs.access: - self.msg = self.msg + ["{0}/{1}/logs/access.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.nginx: - self.msg = self.msg + ["{0}/{1}/logs/error.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - if self.app.pargs.wp: - if os.path.isdir('{0}/htdocs/wp-content'.format(webroot)): - if not os.path.isfile('{0}/logs/debug.log' - .format(webroot)): - if not os.path.isfile('{0}/htdocs/wp-content/debug.log' - .format(webroot)): - open("{0}/htdocs/wp-content/debug.log" - .format(webroot), - encoding='utf-8', mode='a').close() - EEShellExec.cmd_exec(self, "chown {1}: {0}/htdocs/" - "wp-content/debug.log" - "".format(webroot, - EEVariables - .ee_php_user) - ) - # create symbolic link for debug log - EEFileUtils.create_symlink(self, ["{0}/htdocs/wp-content/" - "debug.log" - .format(webroot), - '{0}/logs/debug.log' - .format(webroot)]) - - self.msg = self.msg + ["{0}/{1}/logs/debug.log" - .format(EEVariables.ee_webroot, - self.app.pargs.site_name)] - else: - Log.info(self, "Site is not WordPress site, skipping " - "WordPress logs") - - mail_list = [] - for m_list in self.msg: - mail_list = mail_list + glob.glob(m_list) - - for tomail in self.app.pargs.to: - Log.info(self, "Sending mail to {0}".format(tomail[0])) - EESendMail("easyengine", tomail[0], "{0} Log Files" - .format(EEVariables.ee_fqdn), - "Hey Hi,\n Please find attached server log files" - "\n\n\nYour's faithfully,\nEasyEngine", - files=mail_list, port=25, isTls=False) - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EELogController) - handler.register(EELogShowController) - handler.register(EELogResetController) - handler.register(EELogGzipController) - handler.register(EELogMailController) - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_log_hook) diff --git a/ee/cli/plugins/models.py b/ee/cli/plugins/models.py deleted file mode 100644 index 0aa3ff9b6..000000000 --- a/ee/cli/plugins/models.py +++ /dev/null @@ -1,59 +0,0 @@ -from sqlalchemy import Column, DateTime, String, Integer, Boolean, func -from ee.core.database import Base - - -class SiteDB(Base): - """ - Databse model for site table - """ - __tablename__ = 'sites' - __table_args__ = {'extend_existing': True} - id = Column(Integer, primary_key=True) - sitename = Column(String, unique=True) - - site_type = Column(String) - cache_type = Column(String) - site_path = Column(String) - - # Use default=func.now() to set the default created time - # of a site to be the current time when a - # Site record was created - - created_on = Column(DateTime, default=func.now()) - is_enabled = Column(Boolean, unique=False, default=True, nullable=False) - is_ssl = Column(Boolean, unique=False, default=False) - storage_fs = Column(String) - storage_db = Column(String) - db_name = Column(String) - db_user = Column(String) - db_password = Column(String) - db_host = Column(String) - is_hhvm = Column(Boolean, unique=False, default=False) - is_pagespeed = Column(Boolean, unique=False, default=False) - php_version = Column(String) - - def __init__(self, sitename=None, site_type=None, cache_type=None, - site_path=None, site_enabled=None, - is_ssl=None, storage_fs=None, storage_db=None, db_name=None, - db_user=None, db_password=None, db_host='localhost', - hhvm=None, pagespeed=None, php_version=None): - self.sitename = sitename - self.site_type = site_type - self.cache_type = cache_type - self.site_path = site_path - self.is_enabled = site_enabled - self.is_ssl = is_ssl - self.storage_fs = storage_fs - self.storage_db = storage_db - self.db_name = db_name - self.db_user = db_user - self.db_password = db_password - self.db_host = db_host - self.is_hhvm = hhvm - self.is_pagespeed = pagespeed - self.php_version = php_version - # def __repr__(self): - # return '' % (self.site_type) - # - # def getType(self): - # return '%r>' % (self.site_type) diff --git a/ee/cli/plugins/secure.py b/ee/cli/plugins/secure.py deleted file mode 100644 index c87e65900..000000000 --- a/ee/cli/plugins/secure.py +++ /dev/null @@ -1,145 +0,0 @@ -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.aptget import EEAptGet -from ee.core.shellexec import EEShellExec -from ee.core.variables import EEVariables -from ee.core.logging import Log -from ee.core.git import EEGit -from ee.core.services import EEService -import string -import random -import sys -import hashlib -import getpass - - -def ee_secure_hook(app): - # do something with the ``app`` object here. - pass - - -class EESecureController(CementBaseController): - class Meta: - label = 'secure' - stacked_on = 'base' - stacked_type = 'nested' - description = ('Secure command secure auth, ip and port') - arguments = [ - (['--auth'], - dict(help='secure auth', action='store_true')), - (['--port'], - dict(help='secure port', action='store_true')), - (['--ip'], - dict(help='secure ip', action='store_true')), - (['user_input'], - dict(help='user input', nargs='?', default=None)), - (['user_pass'], - dict(help='user pass', nargs='?', default=None))] - usage = "ee secure [options]" - - @expose(hide=True) - def default(self): - if self.app.pargs.auth: - self.secure_auth() - if self.app.pargs.port: - self.secure_port() - if self.app.pargs.ip: - self.secure_ip() - - @expose(hide=True) - def secure_auth(self): - """This function Secures authentication""" - passwd = ''.join([random.choice - (string.ascii_letters + string.digits) - for n in range(6)]) - if not self.app.pargs.user_input: - username = input("Provide HTTP authentication user " - "name [{0}] :".format(EEVariables.ee_user)) - self.app.pargs.user_input = username - if username == "": - self.app.pargs.user_input = EEVariables.ee_user - if not self.app.pargs.user_pass: - password = getpass.getpass("Provide HTTP authentication " - "password [{0}] :".format(passwd)) - self.app.pargs.user_pass = password - if password == "": - self.app.pargs.user_pass = passwd - Log.debug(self, "printf username:" - "$(openssl passwd -crypt " - "password 2> /dev/null)\n\"" - "> /etc/nginx/htpasswd-ee 2>/dev/null") - EEShellExec.cmd_exec(self, "printf \"{username}:" - "$(openssl passwd -crypt " - "{password} 2> /dev/null)\n\"" - "> /etc/nginx/htpasswd-ee 2>/dev/null" - .format(username=self.app.pargs.user_input, - password=self.app.pargs.user_pass), - log=False) - EEGit.add(self, ["/etc/nginx"], - msg="Adding changed secure auth into Git") - - @expose(hide=True) - def secure_port(self): - """This function Secures port""" - if self.app.pargs.user_input: - while not self.app.pargs.user_input.isdigit(): - Log.info(self, "Please Enter valid port number ") - self.app.pargs.user_input = input("EasyEngine " - "admin port [22222]:") - if not self.app.pargs.user_input: - port = input("EasyEngine admin port [22222]:") - if port == "": - self.app.pargs.user_input = 22222 - while not port.isdigit() and port != "": - Log.info(self, "Please Enter valid port number :") - port = input("EasyEngine admin port [22222]:") - self.app.pargs.user_input = port - if EEVariables.ee_platform_distro == 'ubuntu': - EEShellExec.cmd_exec(self, "sed -i \"s/listen.*/listen " - "{port} default_server ssl http2;/\" " - "/etc/nginx/sites-available/22222" - .format(port=self.app.pargs.user_input)) - if EEVariables.ee_platform_distro == 'debian': - EEShellExec.cmd_exec(self, "sed -i \"s/listen.*/listen " - "{port} default_server ssl http2;/\" " - "/etc/nginx/sites-available/22222" - .format(port=self.app.pargs.user_input)) - EEGit.add(self, ["/etc/nginx"], - msg="Adding changed secure port into Git") - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - Log.info(self, "Successfully port changed {port}" - .format(port=self.app.pargs.user_input)) - - @expose(hide=True) - def secure_ip(self): - """This function Secures IP""" - # TODO:remaining with ee.conf updation in file - newlist = [] - if not self.app.pargs.user_input: - ip = input("Enter the comma separated IP addresses " - "to white list [127.0.0.1]:") - self.app.pargs.user_input = ip - try: - user_ip = self.app.pargs.user_input.split(',') - except Exception as e: - user_ip = ['127.0.0.1'] - for ip_addr in user_ip: - if not ("exist_ip_address "+ip_addr in open('/etc/nginx/common/' - 'acl.conf').read()): - EEShellExec.cmd_exec(self, "sed -i " - "\"/deny/i allow {whitelist_adre}\;\"" - " /etc/nginx/common/acl.conf" - .format(whitelist_adre=ip_addr)) - EEGit.add(self, ["/etc/nginx"], - msg="Adding changed secure ip into Git") - - Log.info(self, "Successfully added IP address in acl.conf file") - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EESecureController) - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_secure_hook) diff --git a/ee/cli/plugins/site.py b/ee/cli/plugins/site.py deleted file mode 100644 index 7ac6654d9..000000000 --- a/ee/cli/plugins/site.py +++ /dev/null @@ -1,1890 +0,0 @@ -# """EasyEngine site controller.""" -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.cron import EECron -from ee.core.sslutils import SSL -from ee.core.variables import EEVariables -from ee.core.domainvalidate import ValidateDomain -from ee.core.fileutils import EEFileUtils -from ee.cli.plugins.site_functions import * -from ee.core.services import EEService -from ee.cli.plugins.sitedb import * -from ee.core.git import EEGit -from subprocess import Popen -from ee.core.nginxhashbucket import hashbucket -import sys -import os -import glob -import subprocess - - -def ee_site_hook(app): - # do something with the ``app`` object here. - from ee.core.database import init_db - import ee.cli.plugins.models - init_db(app) - - -class EESiteController(CementBaseController): - class Meta: - label = 'site' - stacked_on = 'base' - stacked_type = 'nested' - description = ('Performs website specific operations') - arguments = [ - (['site_name'], - dict(help='Website name', nargs='?')), - ] - usage = "ee site (command) [options]" - - @expose(hide=True) - def default(self): - self.app.args.print_help() - - @expose(help="Enable site example.com") - def enable(self): - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - except IOError as e: - Log.error(self, 'could not input site name') - - self.app.pargs.site_name = self.app.pargs.site_name.strip() - # validate domain name - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - - # check if site exists - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(ee_domain)): - Log.info(self, "Enable domain {0:10} \t".format(ee_domain), end='') - EEFileUtils.create_symlink(self, - ['/etc/nginx/sites-available/{0}' - .format(ee_domain), - '/etc/nginx/sites-enabled/{0}' - .format(ee_domain)]) - EEGit.add(self, ["/etc/nginx"], - msg="Enabled {0} " - .format(ee_domain)) - updateSiteInfo(self, ee_domain, enabled=True) - Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - else: - Log.error(self, "nginx configuration file does not exist" - .format(ee_domain)) - - @expose(help="Disable site example.com") - def disable(self): - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - - except IOError as e: - Log.error(self, 'could not input site name') - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - # check if site exists - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(ee_domain)): - Log.info(self, "Disable domain {0:10} \t" - .format(ee_domain), end='') - if not os.path.isfile('/etc/nginx/sites-enabled/{0}' - .format(ee_domain)): - Log.debug(self, "Site {0} already disabled".format(ee_domain)) - Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") - else: - EEFileUtils.remove_symlink(self, - '/etc/nginx/sites-enabled/{0}' - .format(ee_domain)) - EEGit.add(self, ["/etc/nginx"], - msg="Disabled {0} " - .format(ee_domain)) - updateSiteInfo(self, ee_domain, enabled=False) - Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - else: - Log.error(self, "nginx configuration file does not exist" - .format(ee_domain)) - - @expose(help="Get example.com information") - def info(self): - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - except IOError as e: - Log.error(self, 'could not input site name') - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - ee_db_name = '' - ee_db_user = '' - ee_db_pass = '' - hhvm = '' - - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(ee_domain)): - siteinfo = getSiteInfo(self, ee_domain) - - sitetype = siteinfo.site_type - cachetype = siteinfo.cache_type - ee_site_webroot = siteinfo.site_path - access_log = (ee_site_webroot + '/logs/access.log') - error_log = (ee_site_webroot + '/logs/error.log') - ee_db_name = siteinfo.db_name - ee_db_user = siteinfo.db_user - ee_db_pass = siteinfo.db_password - ee_db_host = siteinfo.db_host - if sitetype != "html": - hhvm = ("enabled" if siteinfo.is_hhvm else "disabled") - if sitetype == "proxy": - access_log = "/var/log/nginx/{0}.access.log".format(ee_domain) - error_log = "/var/log/nginx/{0}.error.log".format(ee_domain) - ee_site_webroot = '' - - php_version = siteinfo.php_version -# pagespeed = ("enabled" if siteinfo.is_pagespeed else "disabled") - ssl = ("enabled" if siteinfo.is_ssl else "disabled") - if (ssl == "enabled"): - sslprovider = "Lets Encrypt" - sslexpiry = str(SSL.getExpirationDate(self,ee_domain)) - else: - sslprovider = '' - sslexpiry = '' - data = dict(domain=ee_domain, webroot=ee_site_webroot, - accesslog=access_log, errorlog=error_log, - dbname=ee_db_name, dbuser=ee_db_user,php_version=php_version, - dbpass=ee_db_pass, hhvm=hhvm, - ssl=ssl, sslprovider=sslprovider, sslexpiry= sslexpiry, - type=sitetype + " " + cachetype + " ({0})" - .format("enabled" if siteinfo.is_enabled else - "disabled")) - self.app.render((data), 'siteinfo.mustache') - else: - Log.error(self, "nginx configuration file does not exist" - .format(ee_domain)) - - @expose(help="Monitor example.com logs") - def log(self): - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - ee_site_webroot = getSiteInfo(self, ee_domain).site_path - - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - logfiles = glob.glob(ee_site_webroot + '/logs/*.log') - if logfiles: - logwatch(self, logfiles) - - @expose(help="Display Nginx configuration of example.com") - def show(self): - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - except IOError as e: - Log.error(self, 'could not input site name') - # TODO Write code for ee site edit command here - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(ee_domain)): - Log.info(self, "Display NGINX configuration for {0}" - .format(ee_domain)) - f = open('/etc/nginx/sites-available/{0}'.format(ee_domain), - encoding='utf-8', mode='r') - text = f.read() - Log.info(self, Log.ENDC + text) - f.close() - else: - Log.error(self, "nginx configuration file does not exists" - .format(ee_domain)) - - @expose(help="Change directory to site webroot") - def cd(self): - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - except IOError as e: - Log.error(self, 'Unable to read input, please try again') - - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - - ee_site_webroot = getSiteInfo(self, ee_domain).site_path - EEFileUtils.chdir(self, ee_site_webroot) - - try: - subprocess.call(['bash']) - except OSError as e: - Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) - Log.error(self, "unable to change directory") - - -class EESiteEditController(CementBaseController): - class Meta: - label = 'edit' - stacked_on = 'site' - stacked_type = 'nested' - description = ('Edit Nginx configuration of site') - arguments = [ - (['site_name'], - dict(help='domain name for the site', - nargs='?')), - (['--pagespeed'], - dict(help="edit pagespeed configuration for site", - action='store_true')), - ] - - @expose(hide=True) - def default(self): - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - except IOError as e: - Log.error(self, 'Unable to read input, Please try again') - - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - - ee_site_webroot = EEVariables.ee_webroot + ee_domain - - if not self.app.pargs.pagespeed: - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(ee_domain)): - try: - EEShellExec.invoke_editor(self, '/etc/nginx/sites-availa' - 'ble/{0}'.format(ee_domain)) - except CommandExecutionError as e: - Log.error(self, "Failed invoke editor") - if (EEGit.checkfilestatus(self, "/etc/nginx", - '/etc/nginx/sites-available/{0}'.format(ee_domain))): - EEGit.add(self, ["/etc/nginx"], msg="Edit website: {0}" - .format(ee_domain)) - # Reload NGINX - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - else: - Log.error(self, "nginx configuration file does not exists" - .format(ee_domain)) - - elif self.app.pargs.pagespeed: - Log.error(self, "Pagespeed support has been dropped since EasyEngine v3.6.0",False) - Log.error(self, "Please run command again without `--pagespeed`",False) - Log.error(self, "For more details, read - https://easyengine.io/blog/disabling-pagespeed/") - - ''' - if os.path.isfile('{0}/conf/nginx/pagespeed.conf' - .format(ee_site_webroot)): - try: - EEShellExec.invoke_editor(self, '{0}/conf/nginx/' - 'pagespeed.conf' - .format(ee_site_webroot)) - except CommandExecutionError as e: - Log.error(self, "Failed invoke editor") - if (EEGit.checkfilestatus(self, "{0}/conf/nginx" - .format(ee_site_webroot), - '{0}/conf/nginx/pagespeed.conf'.format(ee_site_webroot))): - EEGit.add(self, ["{0}/conf/nginx".format(ee_site_webroot)], - msg="Edit Pagespped config of site: {0}" - .format(ee_domain)) - # Reload NGINX - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - else: - Log.error(self, "Pagespeed configuration file does not exists" - .format(ee_domain)) - ''' - -class EESiteCreateController(CementBaseController): - class Meta: - label = 'create' - stacked_on = 'site' - stacked_type = 'nested' - description = ('this commands set up configuration and installs ' - 'required files as options are provided') - arguments = [ - (['site_name'], - dict(help='domain name for the site to be created.', - nargs='?')), - (['--html'], - dict(help="create html site", action='store_true')), - (['--php'], - dict(help="create php site", action='store_true')), - (['--php7'], - dict(help="create php 7.0 site", action='store_true')), - (['--mysql'], - dict(help="create mysql site", action='store_true')), - (['--wp'], - dict(help="create wordpress single site", - action='store_true')), - (['--wpsubdir'], - dict(help="create wordpress multisite with subdirectory setup", - action='store_true')), - (['--wpsubdomain'], - dict(help="create wordpress multisite with subdomain setup", - action='store_true')), - (['--w3tc'], - dict(help="create wordpress single/multi site with w3tc cache", - action='store_true')), - (['--wpfc'], - dict(help="create wordpress single/multi site with wpfc cache", - action='store_true')), - (['--wpsc'], - dict(help="create wordpress single/multi site with wpsc cache", - action='store_true')), - (['--wpredis'], - dict(help="create wordpress single/multi site with redis cache", - action='store_true')), - (['--hhvm'], - dict(help="create HHVM site", action='store_true')), - (['--pagespeed'], - dict(help="create pagespeed site", action='store_true')), - (['-le','--letsencrypt'], - dict(help="configure letsencrypt ssl for the site", action='store_true')), - (['--user'], - dict(help="provide user for wordpress site")), - (['--email'], - dict(help="provide email address for wordpress site")), - (['--pass'], - dict(help="provide password for wordpress user", - dest='wppass')), - (['--proxy'], - dict(help="create proxy for site", nargs='+')), - (['--experimental'], - dict(help="Enable Experimenal packages without prompt", - action='store_true')), - ] - - @expose(hide=True) - def default(self): - #Remove Pagespeed Support Since EE 3.6.0 - if self.app.pargs.pagespeed: - Log.error(self, "Pagespeed support has been dropped since EasyEngine v3.6.0",False) - Log.error(self, "Please run command again without `--pagespeed`",False) - Log.error(self, "For more details, read - https://easyengine.io/blog/disabling-pagespeed/") - # self.app.render((data), 'default.mustache') - # Check domain name validation - data = dict() - host, port = None, None - try: - stype, cache = detSitePar(vars(self.app.pargs)) - except RuntimeError as e: - Log.debug(self, str(e)) - Log.error(self, "Please provide valid options to creating site") - - if stype is None and self.app.pargs.proxy: - stype, cache = 'proxy', '' - proxyinfo = self.app.pargs.proxy[0].strip() - if not proxyinfo: - Log.error(self, "Please provide proxy server host information") - proxyinfo = proxyinfo.split(':') - host = proxyinfo[0].strip() - port = '80' if len(proxyinfo) < 2 else proxyinfo[1].strip() - elif stype is None and not self.app.pargs.proxy: - stype, cache = 'html', 'basic' - elif stype and self.app.pargs.proxy: - Log.error(self, "proxy should not be used with other site types") - if (self.app.pargs.proxy and self.app.pargs.hhvm): - Log.error(self, "Proxy site can not run on hhvm") - - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - # preprocessing before finalize site name - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - except IOError as e: - Log.debug(self, str(e)) - Log.error(self, "Unable to input site name, Please try again!") - - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - - if not ee_domain.strip(): - Log.error("Invalid domain name, " - "Provide valid domain name") - - ee_site_webroot = EEVariables.ee_webroot + ee_domain - - if check_domain_exists(self, ee_domain): - Log.error(self, "site {0} already exists".format(ee_domain)) - elif os.path.isfile('/etc/nginx/sites-available/{0}' - .format(ee_domain)): - Log.error(self, "Nginx configuration /etc/nginx/sites-available/" - "{0} already exists".format(ee_domain)) - - if stype == 'proxy': - data['site_name'] = ee_domain - data['www_domain'] = ee_www_domain - data['proxy'] = True - data['host'] = host - data['port'] = port - ee_site_webroot = "" - - if self.app.pargs.php7: - data = dict(site_name=ee_domain, www_domain=ee_www_domain, - static=False, basic=False, php7=True, wp=False, w3tc=False, - wpfc=False, wpsc=False, multisite=False, - wpsubdir=False, webroot=ee_site_webroot) - data['basic'] = True - - if stype in ['html', 'php' ]: - data = dict(site_name=ee_domain, www_domain=ee_www_domain, - static=True, basic=False, php7=False, wp=False, w3tc=False, - wpfc=False, wpsc=False, multisite=False, - wpsubdir=False, webroot=ee_site_webroot) - - if stype == 'php': - data['static'] = False - data['basic'] = True - - elif stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - - data = dict(site_name=ee_domain, www_domain=ee_www_domain, - static=False, basic=True, wp=False, w3tc=False, - wpfc=False, wpsc=False, wpredis=False, multisite=False, - wpsubdir=False, webroot=ee_site_webroot, - ee_db_name='', ee_db_user='', ee_db_pass='', - ee_db_host='') - - if stype in ['wp', 'wpsubdir', 'wpsubdomain']: - data['wp'] = True - data['basic'] = False - data[cache] = True - data['wp-user'] = self.app.pargs.user - data['wp-email'] = self.app.pargs.email - data['wp-pass'] = self.app.pargs.wppass - if stype in ['wpsubdir', 'wpsubdomain']: - data['multisite'] = True - if stype == 'wpsubdir': - data['wpsubdir'] = True - else: - pass - - if stype == "html" and self.app.pargs.hhvm: - Log.error(self, "Can not create HTML site with HHVM") - - if data and self.app.pargs.php7: - if (not self.app.pargs.experimental): - Log.info(self, "PHP7.0 is experimental feature and it may not " - "work with all CSS/JS/Cache of your site.\nDo you wish" - " to install PHP 7.0 now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]:") - if check_prompt != "Y" and check_prompt != "y": - Log.info(self, "Not using PHP 7.0 for site.") - data['php7'] = False - data['basic'] = True - php7 = 0 - self.app.pargs.php7 = False - else: - data['php7'] = True - php7 = 1 - else: - data['php7'] = True - php7 = 1 - elif data: - data['php7'] = False - php7 = 0 - - if (not self.app.pargs.w3tc) and\ - (not self.app.pargs.wpfc) and (not self.app.pargs.wpsc) and (not self.app.pargs.wpredis) \ - and (not self.app.pargs.hhvm): - data['basic'] = True - - #for debug purpose - #for key, value in data.items() : - # print (key, value) - - - if data and self.app.pargs.hhvm: - if (not self.app.pargs.experimental): - Log.info(self, "HHVM is experimental feature and it may not " - "work with all plugins of your site.\nYou can " - "disable it by passing --hhvm=off later.\nDo you wish" - " to enable HHVM now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]:") - if check_prompt != "Y" and check_prompt != "y": - Log.info(self, "Not using HHVM for site.") - data['hhvm'] = False - hhvm = 0 - self.app.pargs.hhvm = False - else: - data['hhvm'] = True - hhvm = 1 - else: - data['hhvm'] = True - hhvm = 1 - - elif data: - data['hhvm'] = False - hhvm = 0 - -# if data and self.app.pargs.pagespeed: -# if (not self.app.pargs.experimental): -# Log.info(self, "PageSpeed is experimental feature and it may not " -# "work with all CSS/JS/Cache of your site.\nYou can " -# "disable it by passing --pagespeed=off later.\nDo you wish" -# " to enable PageSpeed now for {0}?".format(ee_domain)) - - # Check prompt -# check_prompt = input("Type \"y\" to continue [n]:") -# if check_prompt != "Y" and check_prompt != "y": -# Log.info(self, "Not using PageSpeed for site.") -# data['pagespeed'] = False -# pagespeed = 0 -# self.app.pargs.pagespeed = False -# else: -# data['pagespeed'] = True -# pagespeed = 1 -# else: -# data['pagespeed'] = True -# pagespeed = 1 -# elif data: -# data['pagespeed'] = False -# pagespeed = 0 - - if (cache == 'wpredis' and (not self.app.pargs.experimental)): - Log.info(self, "Redis is experimental feature and it may not " - "work with all CSS/JS/Cache of your site.\nYou can " - "disable it by changing cache later.\nDo you wish" - " to enable Redis now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]:") - if check_prompt != "Y" and check_prompt != "y": - Log.error(self, "Not using Redis for site") - cache = 'basic' - data['wpredis'] = False - data['basic'] = True - self.app.pargs.wpredis = False - - # Check rerequired packages are installed or not - ee_auth = site_package_check(self, stype) - - try: - pre_run_checks(self) - except SiteError as e: - Log.debug(self, str(e)) - Log.error(self, "NGINX configuration check failed.") - - try: - try: - # setup NGINX configuration, and webroot - setupdomain(self, data) - - # Fix Nginx Hashbucket size error - hashbucket(self) - except SiteError as e: - # call cleanup actions on failure - Log.info(self, Log.FAIL + "Oops Something went wrong !!") - Log.info(self, Log.FAIL + "Calling cleanup actions ...") - doCleanupAction(self, domain=ee_domain, - webroot=data['webroot']) - Log.debug(self, str(e)) - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - if 'proxy' in data.keys() and data['proxy']: - addNewSite(self, ee_domain, stype, cache, ee_site_webroot) - # Service Nginx Reload - if not EEService.reload_service(self, 'nginx'): - Log.info(self, Log.FAIL + "Oops Something went wrong !!") - Log.info(self, Log.FAIL + "Calling cleanup actions ...") - doCleanupAction(self, domain=ee_domain) - deleteSiteInfo(self, ee_domain) - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - if ee_auth and len(ee_auth): - for msg in ee_auth: - Log.info(self, Log.ENDC + msg, log=False) - Log.info(self, "Successfully created site" - " http://{0}".format(ee_domain)) - return - # Update pagespeed config -# if self.app.pargs.pagespeed: -# operateOnPagespeed(self, data) - - if data['php7']: - php_version = "7.0" - else: - php_version = "5.6" - - - addNewSite(self, ee_domain, stype, cache, ee_site_webroot, - hhvm=hhvm, php_version=php_version) - - # Setup database for MySQL site - if 'ee_db_name' in data.keys() and not data['wp']: - try: - data = setupdatabase(self, data) - # Add database information for site into database - updateSiteInfo(self, ee_domain, db_name=data['ee_db_name'], - db_user=data['ee_db_user'], - db_password=data['ee_db_pass'], - db_host=data['ee_db_host']) - except SiteError as e: - # call cleanup actions on failure - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Oops Something went wrong !!") - Log.info(self, Log.FAIL + "Calling cleanup actions ...") - doCleanupAction(self, domain=ee_domain, - webroot=data['webroot'], - dbname=data['ee_db_name'], - dbuser=data['ee_db_user'], - dbhost=data['ee_db_host']) - deleteSiteInfo(self, ee_domain) - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - try: - eedbconfig = open("{0}/ee-config.php" - .format(ee_site_webroot), - encoding='utf-8', mode='w') - eedbconfig.write("" - .format(data['ee_db_name'], - data['ee_db_user'], - data['ee_db_pass'], - data['ee_db_host'])) - eedbconfig.close() - stype = 'mysql' - except IOError as e: - Log.debug(self, str(e)) - Log.debug(self, "Error occured while generating " - "ee-config.php") - Log.info(self, Log.FAIL + "Oops Something went wrong !!") - Log.info(self, Log.FAIL + "Calling cleanup actions ...") - doCleanupAction(self, domain=ee_domain, - webroot=data['webroot'], - dbname=data['ee_db_name'], - dbuser=data['ee_db_user'], - dbhost=data['ee_db_host']) - deleteSiteInfo(self, ee_domain) - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - # Setup WordPress if Wordpress site - if data['wp']: - try: - ee_wp_creds = setupwordpress(self, data) - # Add database information for site into database - updateSiteInfo(self, ee_domain, db_name=data['ee_db_name'], - db_user=data['ee_db_user'], - db_password=data['ee_db_pass'], - db_host=data['ee_db_host']) - except SiteError as e: - # call cleanup actions on failure - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Oops Something went wrong !!") - Log.info(self, Log.FAIL + "Calling cleanup actions ...") - doCleanupAction(self, domain=ee_domain, - webroot=data['webroot'], - dbname=data['ee_db_name'], - dbuser=data['ee_db_user'], - dbhost=data['ee_mysql_grant_host']) - deleteSiteInfo(self, ee_domain) - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - # Service Nginx Reload call cleanup if failed to reload nginx - if not EEService.reload_service(self, 'nginx'): - Log.info(self, Log.FAIL + "Oops Something went wrong !!") - Log.info(self, Log.FAIL + "Calling cleanup actions ...") - doCleanupAction(self, domain=ee_domain, - webroot=data['webroot']) - if 'ee_db_name' in data.keys(): - doCleanupAction(self, domain=ee_domain, - dbname=data['ee_db_name'], - dbuser=data['ee_db_user'], - dbhost=data['ee_mysql_grant_host']) - deleteSiteInfo(self, ee_domain) - Log.info(self, Log.FAIL + "service nginx reload failed." - " check issues with `nginx -t` command.") - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - EEGit.add(self, ["/etc/nginx"], - msg="{0} created with {1} {2}" - .format(ee_www_domain, stype, cache)) - # Setup Permissions for webroot - try: - setwebrootpermissions(self, data['webroot']) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Oops Something went wrong !!") - Log.info(self, Log.FAIL + "Calling cleanup actions ...") - doCleanupAction(self, domain=ee_domain, - webroot=data['webroot']) - if 'ee_db_name' in data.keys(): - print("Inside db cleanup") - doCleanupAction(self, domain=ee_domain, - dbname=data['ee_db_name'], - dbuser=data['ee_db_user'], - dbhost=data['ee_mysql_grant_host']) - deleteSiteInfo(self, ee_domain) - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - if ee_auth and len(ee_auth): - for msg in ee_auth: - Log.info(self, Log.ENDC + msg, log=False) - - if data['wp']: - Log.info(self, Log.ENDC + "WordPress admin user :" - " {0}".format(ee_wp_creds['wp_user']), log=False) - Log.info(self, Log.ENDC + "WordPress admin user password : {0}" - .format(ee_wp_creds['wp_pass']), log=False) - - display_cache_settings(self, data) - - Log.info(self, "Successfully created site" - " http://{0}".format(ee_domain)) - except SiteError as e: - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - if self.app.pargs.letsencrypt : - if (not self.app.pargs.experimental): - if stype in ['wpsubdomain']: - Log.warn(self, "Wildcard domains are not supported in Lets Encrypt.\nWP SUBDOMAIN site will get SSL for primary site only.") - - Log.info(self, "Letsencrypt is currently in beta phase." - " \nDo you wish" - " to enable SSl now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]:") - if check_prompt != "Y" and check_prompt != "y": - data['letsencrypt'] = False - letsencrypt = False - else: - data['letsencrypt'] = True - letsencrypt = True - else: - data['letsencrypt'] = True - letsencrypt = True - - if data['letsencrypt'] is True: - setupLetsEncrypt(self, ee_domain) - httpsRedirect(self,ee_domain) - Log.info(self,"Creating Cron Job for cert auto-renewal") - EECron.setcron_weekly(self,'ee site update --le=renew --all 2> /dev/null'.format(ee_domain),'Renew all' - ' letsencrypt SSL cert. Set by EasyEngine') - - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - - Log.info(self, "Congratulations! Successfully Configured SSl for Site " - " https://{0}".format(ee_domain)) - - if (SSL.getExpirationDays(self,ee_domain)>0): - Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,ee_domain)) + " days.") - else: - Log.warn(self, "Your cert already EXPIRED ! .PLEASE renew soon . ") - - # Add nginx conf folder into GIT - EEGit.add(self, ["{0}/conf/nginx".format(ee_site_webroot)], - msg="Adding letsencrypts config of site: {0}" - .format(ee_domain)) - updateSiteInfo(self, ee_domain, ssl=letsencrypt) - - elif data['letsencrypt'] is False: - Log.info(self, "Not using Let\'s encrypt for Site " - " http://{0}".format(ee_domain)) - - - - -class EESiteUpdateController(CementBaseController): - class Meta: - label = 'update' - stacked_on = 'site' - stacked_type = 'nested' - description = ('This command updates websites configuration to ' - 'another as per the options are provided') - arguments = [ - (['site_name'], - dict(help='domain name for the site to be updated', - nargs='?')), - (['--password'], - dict(help="update to password for wordpress site user", - action='store_true')), - (['--html'], - dict(help="update to html site", action='store_true')), - (['--php'], - dict(help="update to php site", action='store_true')), - (['--php7'], - dict(help="update to php7 site", - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--mysql'], - dict(help="update to mysql site", action='store_true')), - (['--wp'], - dict(help="update to wordpress single site", - action='store_true')), - (['--wpsubdir'], - dict(help="update to wpsubdir site", action='store_true')), - (['--wpsubdomain'], - dict(help="update to wpsubdomain site", action='store_true')), - (['--w3tc'], - dict(help="update to w3tc cache", action='store_true')), - (['--wpfc'], - dict(help="update to wpfc cache", action='store_true')), - (['--wpsc'], - dict(help="update to wpsc cache", action='store_true')), - (['--wpredis'], - dict(help="update to redis cache", action='store_true')), - (['--hhvm'], - dict(help='Use HHVM for site', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['--pagespeed'], - dict(help='Use PageSpeed for site', - action='store' or 'store_const', - choices=('on', 'off'), const='on', nargs='?')), - (['-le','--letsencrypt'], - dict(help="configure letsencrypt ssl for the site", - action='store' or 'store_const', - choices=('on', 'off', 'renew'), const='on', nargs='?')), - (['--proxy'], - dict(help="update to proxy site", nargs='+')), - (['--experimental'], - dict(help="Enable Experimenal packages without prompt", - action='store_true')), - (['--all'], - dict(help="update all sites", action='store_true')), - ] - - @expose(help="Update site type or cache") - def default(self): - pargs = self.app.pargs - - if self.app.pargs.pagespeed: - Log.error(self, "Pagespeed support has been dropped since EasyEngine v3.6.0",False) - Log.error(self, "Please run command again without `--pagespeed`",False) - Log.error(self, "For more details, read - https://easyengine.io/blog/disabling-pagespeed/") - - if pargs.all: - if pargs.site_name: - Log.error(self, "`--all` option cannot be used with site name" - " provided") - if pargs.html: - Log.error(self, "No site can be updated to html") - - if not (pargs.php or pargs.php7 or - pargs.mysql or pargs.wp or pargs.wpsubdir or - pargs.wpsubdomain or pargs.w3tc or pargs.wpfc or - pargs.wpsc or pargs.hhvm or pargs.wpredis or pargs.letsencrypt): - Log.error(self, "Please provide options to update sites.") - - if pargs.all: - if pargs.site_name: - Log.error(self, "`--all` option cannot be used with site name" - " provided") - - sites = getAllsites(self) - if not sites: - pass - else: - for site in sites: - pargs.site_name = site.sitename - Log.info(self, Log.ENDC + Log.BOLD + "Updating site {0}," - " please wait..." - .format(pargs.site_name)) - self.doupdatesite(pargs) - print("\n") - else: - self.doupdatesite(pargs) - - def doupdatesite(self, pargs): - hhvm = None - # pagespeed = None - letsencrypt = False - php7 = None - - - data = dict() - try: - stype, cache = detSitePar(vars(pargs)) - except RuntimeError as e: - Log.debug(self, str(e)) - Log.error(self, "Please provide valid options combination for" - " site update") - - if stype is None and pargs.proxy: - stype, cache = 'proxy', '' - proxyinfo = pargs.proxy[0].strip() - if not proxyinfo: - Log.error(self, "Please provide proxy server host information") - proxyinfo = proxyinfo.split(':') - host = proxyinfo[0].strip() - port = '80' if len(proxyinfo) < 2 else proxyinfo[1].strip() - elif stype is None and not (pargs.proxy or pargs.letsencrypt): - stype, cache = 'html', 'basic' - elif stype and pargs.proxy: - Log.error(self, "--proxy can not be used with other site types") - if (pargs.proxy and pargs.hhvm): - Log.error(self, "Proxy site can not run on hhvm") - - if not pargs.site_name: - try: - while not pargs.site_name: - pargs.site_name = (input('Enter site name : ').strip()) - except IOError as e: - Log.error(self, 'Unable to input site name, Please try again!') - - pargs.site_name = pargs.site_name.strip() - (ee_domain, - ee_www_domain, ) = ValidateDomain(pargs.site_name) - ee_site_webroot = EEVariables.ee_webroot + ee_domain - - check_site = getSiteInfo(self, ee_domain) - - if check_site is None: - Log.error(self, " Site {0} does not exist.".format(ee_domain)) - else: - oldsitetype = check_site.site_type - oldcachetype = check_site.cache_type - old_hhvm = check_site.is_hhvm - # old_pagespeed = check_site.is_pagespeed - check_ssl = check_site.is_ssl - check_php_version = check_site.php_version - - if check_php_version == "7.0": - old_php7 = True - else: - old_php7 = False - - if (pargs.password and not (pargs.html or - pargs.php or pargs.php7 or pargs.mysql or pargs.wp or - pargs.w3tc or pargs.wpfc or pargs.wpsc - or pargs.wpsubdir or pargs.wpsubdomain)): - try: - updatewpuserpassword(self, ee_domain, ee_site_webroot) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, "\nPassword Unchanged.") - return 0 - - if ((stype == "proxy" and stype == oldsitetype and self.app.pargs.hhvm) - or (stype == "proxy" and - stype == oldsitetype )): - Log.info(self, Log.FAIL + - "Can not update proxy site to HHVM") - return 1 - if stype == "html" and stype == oldsitetype and self.app.pargs.hhvm: - Log.info(self, Log.FAIL + "Can not update HTML site to HHVM") - return 1 - - if ((stype == 'php' and oldsitetype not in ['html', 'proxy', 'php7']) or - # (stype == 'php7' and oldsitetype not in ['html', 'mysql', 'php', 'php7', 'wp', 'wpsubdir', 'wpsubdomain', ]) or - (stype == 'mysql' and oldsitetype not in ['html', 'php', - 'proxy','php7']) or - (stype == 'wp' and oldsitetype not in ['html', 'php', 'mysql', - 'proxy', 'wp', 'php7']) or - (stype == 'wpsubdir' and oldsitetype in ['wpsubdomain']) or - (stype == 'wpsubdomain' and oldsitetype in ['wpsubdir']) or - (stype == oldsitetype and cache == oldcachetype) and - not pargs.php7): - Log.info(self, Log.FAIL + "can not update {0} {1} to {2} {3}". - format(oldsitetype, oldcachetype, stype, cache)) - return 1 - - if stype == 'proxy': - data['site_name'] = ee_domain - data['www_domain'] = ee_www_domain - data['proxy'] = True - data['host'] = host - data['port'] = port -# pagespeed = False - hhvm = False - data['webroot'] = ee_site_webroot - data['currsitetype'] = oldsitetype - data['currcachetype'] = oldcachetype - - if stype == 'php': - data = dict(site_name=ee_domain, www_domain=ee_www_domain, - static=False, basic=True, wp=False, w3tc=False, - wpfc=False, wpsc=False, wpredis=False, multisite=False, - wpsubdir=False, webroot=ee_site_webroot, - currsitetype=oldsitetype, currcachetype=oldcachetype) - - elif stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - - data = dict(site_name=ee_domain, www_domain=ee_www_domain, - static=False, basic=True, wp=False, w3tc=False, - wpfc=False, wpsc=False, wpredis=False, multisite=False, - wpsubdir=False, webroot=ee_site_webroot, - ee_db_name='', ee_db_user='', ee_db_pass='', - ee_db_host='', - currsitetype=oldsitetype, currcachetype=oldcachetype) - - if stype in ['wp', 'wpsubdir', 'wpsubdomain']: - data['wp'] = True - data['basic'] = False - data[cache] = True - if stype in ['wpsubdir', 'wpsubdomain']: - data['multisite'] = True - if stype == 'wpsubdir': - data['wpsubdir'] = True - - if pargs.hhvm or pargs.php7: - if not data: - data = dict(site_name=ee_domain, www_domain=ee_www_domain, - currsitetype=oldsitetype, - currcachetype=oldcachetype, - webroot=ee_site_webroot) - stype = oldsitetype - cache = oldcachetype - if oldsitetype == 'html' or oldsitetype == 'proxy': - data['static'] = True - data['wp'] = False - data['multisite'] = False - data['wpsubdir'] = False - elif oldsitetype == 'php' or oldsitetype == 'mysql': - data['static'] = False - data['wp'] = False - data['multisite'] = False - data['wpsubdir'] = False - elif oldsitetype == 'wp': - data['static'] = False - data['wp'] = True - data['multisite'] = False - data['wpsubdir'] = False - elif oldsitetype == 'wpsubdir': - data['static'] = False - data['wp'] = True - data['multisite'] = True - data['wpsubdir'] = True - elif oldsitetype == 'wpsubdomain': - data['static'] = False - data['wp'] = True - data['multisite'] = True - data['wpsubdir'] = False - - if oldcachetype == 'basic': - data['basic'] = True - data['w3tc'] = False - data['wpfc'] = False - data['wpsc'] = False - data['wpredis'] = False - elif oldcachetype == 'w3tc': - data['basic'] = False - data['w3tc'] = True - data['wpfc'] = False - data['wpsc'] = False - data['wpredis'] = False - elif oldcachetype == 'wpfc': - data['basic'] = False - data['w3tc'] = False - data['wpfc'] = True - data['wpsc'] = False - data['wpredis'] = False - elif oldcachetype == 'wpsc': - data['basic'] = False - data['w3tc'] = False - data['wpfc'] = False - data['wpsc'] = True - data['wpredis'] = False - elif oldcachetype == 'wpredis': - data['basic'] = False - data['w3tc'] = False - data['wpfc'] = False - data['wpsc'] = False - data['wpredis'] = True - - if pargs.hhvm != 'off': - data['hhvm'] = True - hhvm = True - elif pargs.hhvm == 'off': - data['hhvm'] = False - hhvm = False - -# if pargs.pagespeed != 'off': -# data['pagespeed'] = True -# pagespeed = True -# elif pargs.pagespeed == 'off': -# data['pagespeed'] = False -# pagespeed = False - - if pargs.php7 == 'on' : - data['php7'] = True - php7 = True - check_php_version= '7.0' - elif pargs.php7 == 'off': - data['php7'] = False - php7 = False - check_php_version = '5.6' - -# if pargs.pagespeed: -# if pagespeed is old_pagespeed: -# if pagespeed is False: -# Log.info(self, "Pagespeed is already disabled for given " -# "site") -# elif pagespeed is True: -# Log.info(self, "Pagespeed is already enabled for given " -# "site") -# pargs.pagespeed = False - - if pargs.php7: - if php7 is old_php7: - if php7 is False: - Log.info(self, "PHP 7.0 is already disabled for given " - "site") - elif php7 is True: - Log.info(self, "PHP 7.0 is already enabled for given " - "site") - pargs.php7 = False - - #--letsencrypt=renew code goes here - if pargs.letsencrypt == "renew" and not pargs.all: - expiry_days = SSL.getExpirationDays(self,ee_domain) - min_expiry_days = 30 - if check_ssl: - if (expiry_days <= min_expiry_days): - renewLetsEncrypt(self,ee_domain) - else: - Log.error(self,"More than 30 days left for certificate Expiry. Not renewing now.") - - else: - Log.error(self,"Cannot RENEW ! SSL is not configured for given site .") - - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - Log.info(self, "SUCCESS: Certificate was successfully renewed For" - " https://{0}".format(ee_domain)) - if (SSL.getExpirationDays(self,ee_domain)>0): - Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,ee_domain)) + " days.") - Log.info(self, "Expiration DATE: " + str(SSL.getExpirationDate(self,ee_domain))) - - else: - Log.warn(self, "Your cert already EXPIRED !. PLEASE renew soon . ") - return 0 - - if pargs.all and pargs.letsencrypt == "renew": - - if check_ssl: - expiry_days = SSL.getExpirationDays(self,ee_domain,True) - if expiry_days < 0: - return 0 - min_expiry_days = 30 - if (expiry_days <= min_expiry_days): - renewLetsEncrypt(self,ee_domain) - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - Log.info(self, "SUCCESS: Certificate was successfully renewed For" - " https://{0}".format(ee_domain)) - else: - Log.info(self,"More than 30 days left for certificate Expiry. Not renewing now.\n") - - if (SSL.getExpirationDays(self,ee_domain)>0): - Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,ee_domain)) + " days.") - Log.info(self, "Expiration DATE: \n\n" + str(SSL.getExpirationDate(self,ee_domain))) - return 0 - #else: - # Log.warn(self, "Your cert already EXPIRED ! .PLEASE renew soon . ") - else: - Log.info(self,"SSL not configured for site http://{0}".format(ee_domain)) - return 0 - - if pargs.all and pargs.letsencrypt == "off": - if letsencrypt is check_ssl: - if letsencrypt is False: - Log.error(self, "SSl is not configured for given " - "site",False) - return 0 - pass - - if pargs.letsencrypt: - if pargs.letsencrypt == 'on': - data['letsencrypt'] = True - letsencrypt = True - elif pargs.letsencrypt == 'off': - data['letsencrypt'] = False - letsencrypt = False - - if letsencrypt is check_ssl: - if letsencrypt is False: - Log.error(self, "SSl is not configured for given " - "site") - elif letsencrypt is True: - Log.error(self, "SSl is already configured for given " - "site") - pargs.letsencrypt = False - - if pargs.hhvm: - if hhvm is old_hhvm: - if hhvm is False: - Log.info(self, "HHVM is allready disabled for given " - "site") - elif hhvm is True: - Log.info(self, "HHVM is allready enabled for given " - "site") - - pargs.hhvm = False - - if data and (not pargs.hhvm): - if old_hhvm is True: - data['hhvm'] = True - hhvm = True - else: - data['hhvm'] = False - hhvm = False - -# if data and (not pargs.pagespeed): -# if old_pagespeed is True: -# data['pagespeed'] = True -# pagespeed = True -# else: -# data['pagespeed'] = False -# pagespeed = False - - if data and (not pargs.php7): - if old_php7 is True: - data['php7'] = True - php7 = True - else: - data['php7'] = False - php7 = False - - if pargs.hhvm=="on" or pargs.letsencrypt=="on" or pargs.php7=="on": - if pargs.php7 == "on": - if (not pargs.experimental): - Log.info(self, "PHP7.0 is experimental feature and it may not" - " work with all plugins of your site.\nYou can " - "disable it by passing --php7=off later.\nDo you wish" - " to enable PHP now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]:") - if check_prompt != "Y" and check_prompt != "y": - Log.info(self, "Not using PHP 7.0 for site") - data['php7'] = False - php7 = False - else: - data['php7'] = True - php7 = True - else: - data['php7'] = True - php7 = True - - if pargs.hhvm == "on": - if (not pargs.experimental): - Log.info(self, "HHVM is experimental feature and it may not" - " work with all plugins of your site.\nYou can " - "disable it by passing --hhvm=off later.\nDo you wish" - " to enable HHVM now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]:") - if check_prompt != "Y" and check_prompt != "y": - Log.info(self, "Not using HHVM for site") - data['hhvm'] = False - hhvm = False - else: - data['hhvm'] = True - hhvm = True - else: - data['hhvm'] = True - hhvm = True - - if pargs.letsencrypt == "on": - - if (not pargs.experimental): - - if oldsitetype in ['wpsubdomain']: - Log.warn(self, "Wildcard domains are not supported in Lets Encrypt.\nWP SUBDOMAIN site will get SSL for primary site only.") - - Log.info(self, "Letsencrypt is currently in beta phase." - " \nDo you wish" - " to enable SSl now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]:") - if check_prompt != "Y" and check_prompt != "y": - Log.info(self, "Not using letsencrypt for site") - data['letsencrypt'] = False - letsencrypt = False - else: - data['letsencrypt'] = True - letsencrypt = True - else: - data['letsencrypt'] = True - letsencrypt = True - - - - if pargs.wpredis and data['currcachetype'] != 'wpredis': - if (not pargs.experimental): - Log.info(self, "Redis is experimental feature and it may not" - " work with all plugins of your site.\nYou can " - "disable it by changing cache type later.\nDo you wish" - " to enable Redis now for {0}?".format(ee_domain)) - - # Check prompt - check_prompt = input("Type \"y\" to continue [n]: ") - if check_prompt != "Y" and check_prompt != "y": - Log.error(self, "Not using Redis for site") - data['wpredis'] = False - data['basic'] = True - cache = 'basic' - - if ((hhvm is old_hhvm) and (php7 is old_php7) and - (stype == oldsitetype and cache == oldcachetype)): - return 1 - - if not data: - Log.error(self, "Cannot update {0}, Invalid Options" - .format(ee_domain)) - - ee_auth = site_package_check(self, stype) - data['ee_db_name'] = check_site.db_name - data['ee_db_user'] = check_site.db_user - data['ee_db_pass'] = check_site.db_password - data['ee_db_host'] = check_site.db_host -# data['old_pagespeed_status'] = check_site.is_pagespeed - - if not pargs.letsencrypt: - try: - pre_run_checks(self) - except SiteError as e: - Log.debug(self, str(e)) - Log.error(self, "NGINX configuration check failed.") - - try: - sitebackup(self, data) - except Exception as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - # setup NGINX configuration, and webroot - try: - setupdomain(self, data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason" - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if 'proxy' in data.keys() and data['proxy']: - updateSiteInfo(self, ee_domain, stype=stype, cache=cache, - hhvm=hhvm,ssl=True if check_site.is_ssl else False) - Log.info(self, "Successfully updated site" - " http://{0}".format(ee_domain)) - return 0 - - # Update pagespeed config -# if pargs.pagespeed: -# operateOnPagespeed(self, data) - - if pargs.letsencrypt: - if data['letsencrypt'] is True: - if not os.path.isfile("{0}/conf/nginx/ssl.conf.disabled" - .format(ee_site_webroot)): - setupLetsEncrypt(self, ee_domain) - - else: - EEFileUtils.mvfile(self, "{0}/conf/nginx/ssl.conf.disabled" - .format(ee_site_webroot), - '{0}/conf/nginx/ssl.conf' - .format(ee_site_webroot)) - - httpsRedirect(self,ee_domain) - Log.info(self,"Creating Cron Job for cert auto-renewal") - EECron.setcron_weekly(self,'ee site update --le=renew --all 2> /dev/null'.format(ee_domain),'Renew all' - ' letsencrypt SSL cert. Set by EasyEngine') - - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - - Log.info(self, "Congratulations! Successfully Configured SSl for Site " - " https://{0}".format(ee_domain)) - - if (SSL.getExpirationDays(self,ee_domain)>0): - Log.info(self, "Your cert will expire within " + str(SSL.getExpirationDays(self,ee_domain)) + " days.") - else: - Log.warn(self, "Your cert already EXPIRED ! .PLEASE renew soon . ") - - elif data['letsencrypt'] is False: - if os.path.isfile("{0}/conf/nginx/ssl.conf" - .format(ee_site_webroot)): - Log.info(self,'Setting Nginx configuration') - EEFileUtils.mvfile(self, "{0}/conf/nginx/ssl.conf" - .format(ee_site_webroot), - '{0}/conf/nginx/ssl.conf.disabled' - .format(ee_site_webroot)) - httpsRedirect(self,ee_domain,False) - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - #Log.info(self,"Removing Cron Job set for cert auto-renewal") - #EECron.remove_cron(self,'ee site update {0} --le=renew --min_expiry_limit 30 2> \/dev\/null'.format(ee_domain)) - Log.info(self, "Successfully Disabled SSl for Site " - " http://{0}".format(ee_domain)) - - - # Add nginx conf folder into GIT - EEGit.add(self, ["{0}/conf/nginx".format(ee_site_webroot)], - msg="Adding letsencrypts config of site: {0}" - .format(ee_domain)) - updateSiteInfo(self, ee_domain, ssl=letsencrypt) - return 0 - - if stype == oldsitetype and cache == oldcachetype: - - # Service Nginx Reload - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - - updateSiteInfo(self, ee_domain, stype=stype, cache=cache, - hhvm=hhvm, ssl=True if check_site.is_ssl else False, php_version=check_php_version) - - Log.info(self, "Successfully updated site" - " http://{0}".format(ee_domain)) - return 0 - - #if data['ee_db_name'] and not data['wp']: - if 'ee_db_name' in data.keys() and not data['wp']: - try: - data = setupdatabase(self, data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason" - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - try: - eedbconfig = open("{0}/ee-config.php".format(ee_site_webroot), - encoding='utf-8', mode='w') - eedbconfig.write("" - .format(data['ee_db_name'], - data['ee_db_user'], - data['ee_db_pass'], - data['ee_db_host'])) - eedbconfig.close() - except IOError as e: - Log.debug(self, str(e)) - Log.debug(self, "creating ee-config.php failed.") - Log.info(self, Log.FAIL + "Update site failed. " - "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - # Setup WordPress if old sites are html/php/mysql sites - if data['wp'] and oldsitetype in ['html', 'proxy', 'php', 'mysql']: - try: - ee_wp_creds = setupwordpress(self, data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - # Uninstall unnecessary plugins - if oldsitetype in ['wp', 'wpsubdir', 'wpsubdomain']: - # Setup WordPress Network if update option is multisite - # and oldsite is WordPress single site - if data['multisite'] and oldsitetype == 'wp': - try: - setupwordpressnetwork(self, data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed. " - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if (oldcachetype == 'w3tc' or oldcachetype == 'wpfc' and - not (data['w3tc'] or data['wpfc'])): - try: - uninstallwp_plugin(self, 'w3-total-cache', data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed. " - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if ((oldcachetype in ['w3tc', 'wpsc', 'basic', 'wpredis'] and - (data['wpfc'])) or (oldsitetype == 'wp' and data['multisite'] and data['wpfc'])): - try: - plugin_data = '{"log_level":"INFO","log_filesize":5,"enable_purge":1,"enable_map":0,"enable_log":0,"enable_stamp":0,"purge_homepage_on_new":1,"purge_homepage_on_edit":1,"purge_homepage_on_del":1,"purge_archive_on_new":1,"purge_archive_on_edit":0,"purge_archive_on_del":0,"purge_archive_on_new_comment":0,"purge_archive_on_deleted_comment":0,"purge_page_on_mod":1,"purge_page_on_new_comment":1,"purge_page_on_deleted_comment":1,"cache_method":"enable_fastcgi","purge_method":"get_request","redis_hostname":"127.0.0.1","redis_port":"6379","redis_prefix":"nginx-cache:"}' - setupwp_plugin(self, 'nginx-helper', 'rt_wp_nginx_helper_options', plugin_data, data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update nginx-helper settings failed. " - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - elif ((oldcachetype in ['w3tc', 'wpsc', 'basic', 'wpfc'] and - (data['wpredis'])) or (oldsitetype == 'wp' and data['multisite'] and data['wpredis'])): - try: - plugin_data = '{"log_level":"INFO","log_filesize":5,"enable_purge":1,"enable_map":0,"enable_log":0,"enable_stamp":0,"purge_homepage_on_new":1,"purge_homepage_on_edit":1,"purge_homepage_on_del":1,"purge_archive_on_new":1,"purge_archive_on_edit":0,"purge_archive_on_del":0,"purge_archive_on_new_comment":0,"purge_archive_on_deleted_comment":0,"purge_page_on_mod":1,"purge_page_on_new_comment":1,"purge_page_on_deleted_comment":1,"cache_method":"enable_redis","purge_method":"get_request","redis_hostname":"127.0.0.1","redis_port":"6379","redis_prefix":"nginx-cache:"}' - setupwp_plugin(self, 'nginx-helper', 'rt_wp_nginx_helper_options', plugin_data, data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update nginx-helper settings failed. " - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - else: - try: - plugin_data = '{"log_level":"INFO","log_filesize":5,"enable_purge":0,"enable_map":0,"enable_log":0,"enable_stamp":0,"purge_homepage_on_new":1,"purge_homepage_on_edit":1,"purge_homepage_on_del":1,"purge_archive_on_new":1,"purge_archive_on_edit":0,"purge_archive_on_del":0,"purge_archive_on_new_comment":0,"purge_archive_on_deleted_comment":0,"purge_page_on_mod":1,"purge_page_on_new_comment":1,"purge_page_on_deleted_comment":1,"cache_method":"enable_redis","purge_method":"get_request","redis_hostname":"127.0.0.1","redis_port":"6379","redis_prefix":"nginx-cache:"}' - setupwp_plugin(self, 'nginx-helper', 'rt_wp_nginx_helper_options', plugin_data, data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update nginx-helper settings failed. " - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if oldcachetype == 'wpsc' and not data['wpsc']: - try: - uninstallwp_plugin(self, 'wp-super-cache', data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if oldcachetype == 'wpredis' and not data['wpredis']: - try: - uninstallwp_plugin(self, 'redis-cache', data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if (oldcachetype != 'w3tc' or oldcachetype != 'wpfc') and (data['w3tc'] - or data['wpfc']): - try: - installwp_plugin(self, 'w3-total-cache', data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason" - " `tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if oldcachetype != 'wpsc' and data['wpsc']: - try: - installwp_plugin(self, 'wp-super-cache', data) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if oldcachetype != 'wpredis' and data['wpredis']: - try: - if installwp_plugin(self, 'redis-cache', data): - #search for wp-config.php - if EEFileUtils.isexist(self,"{0}/wp-config.php".format(ee_site_webroot)): - config_path = '{0}/wp-config.php'.format(ee_site_webroot) - elif EEFileUtils.isexist(self,"{0}/htdocs/wp-config.php".format(ee_site_webroot)): - config_path = '{0}/htdocs/wp-config.php'.format(ee_site_webroot) - else: - Log.debug(self, "Updating wp-config.php failed. File could not be located.") - Log.error(self,"wp-config.php could not be located !!") - raise SiteError - - if EEShellExec.cmd_exec(self, "grep -q \"WP_CACHE_KEY_SALT\" {0}" - .format(config_path)): - pass - else: - try: - wpconfig = open("{0}".format(config_path), - encoding='utf-8', mode='a') - wpconfig.write("\n\ndefine( \'WP_CACHE_KEY_SALT\', \'{0}:\' );" - .format(ee_domain)) - wpconfig.close() - except IOError as e: - Log.debug(self, str(e)) - Log.debug(self, "Updating wp-config.php failed.") - Log.warn(self, "Updating wp-config.php failed. " - "Could not append:" - "\ndefine( \'WP_CACHE_KEY_SALT\', \'{0}:\' );".format(ee_domain) + - "\nPlease add manually") - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - # Service Nginx Reload - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "service nginx reload failed. " - "check issues with `nginx -t` command") - - EEGit.add(self, ["/etc/nginx"], - msg="{0} updated with {1} {2}" - .format(ee_www_domain, stype, cache)) - # Setup Permissions for webroot - try: - setwebrootpermissions(self, data['webroot']) - except SiteError as e: - Log.debug(self, str(e)) - Log.info(self, Log.FAIL + "Update site failed." - "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - return 1 - - if ee_auth and len(ee_auth): - for msg in ee_auth: - Log.info(self, Log.ENDC + msg) - - display_cache_settings(self, data) - if data['wp'] and oldsitetype in ['html', 'php', 'mysql']: - Log.info(self, "\n\n" + Log.ENDC + "WordPress admin user :" - " {0}".format(ee_wp_creds['wp_user'])) - Log.info(self, Log.ENDC + "WordPress admin password : {0}" - .format(ee_wp_creds['wp_pass']) + "\n\n") - if oldsitetype in ['html', 'php'] and stype != 'php': - updateSiteInfo(self, ee_domain, stype=stype, cache=cache, - db_name=data['ee_db_name'], - db_user=data['ee_db_user'], - db_password=data['ee_db_pass'], - db_host=data['ee_db_host'], hhvm=hhvm, - ssl=True if check_site.is_ssl else False,php_version=check_php_version) - else: - updateSiteInfo(self, ee_domain, stype=stype, cache=cache, - hhvm=hhvm, ssl=True if check_site.is_ssl else False,php_version=check_php_version) - Log.info(self, "Successfully updated site" - " http://{0}".format(ee_domain)) - return 0 - - -class EESiteDeleteController(CementBaseController): - class Meta: - label = 'delete' - stacked_on = 'site' - stacked_type = 'nested' - description = 'delete an existing website' - arguments = [ - (['site_name'], - dict(help='domain name to be deleted', nargs='?')), - (['--no-prompt'], - dict(help="doesnt ask permission for delete", - action='store_true')), - (['-f','--force'], - dict(help="forcefully delete site and configuration", - action='store_true')), - (['--all'], - dict(help="delete all", action='store_true')), - (['--db'], - dict(help="delete db only", action='store_true')), - (['--files'], - dict(help="delete webroot only", action='store_true')), - ] - - @expose(help="Delete website configuration and files") - @expose(hide=True) - def default(self): - if not self.app.pargs.site_name: - try: - while not self.app.pargs.site_name: - self.app.pargs.site_name = (input('Enter site name : ') - .strip()) - except IOError as e: - Log.error(self, 'could not input site name') - - self.app.pargs.site_name = self.app.pargs.site_name.strip() - (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) - ee_db_name = '' - ee_prompt = '' - ee_nginx_prompt = '' - mark_db_delete_prompt = False - mark_webroot_delete_prompt = False - mark_db_deleted = False - mark_webroot_deleted = False - if not check_domain_exists(self, ee_domain): - Log.error(self, "site {0} does not exist".format(ee_domain)) - - if ((not self.app.pargs.db) and (not self.app.pargs.files) and - (not self.app.pargs.all)): - self.app.pargs.all = True - - # Gather information from ee-db for ee_domain - check_site = getSiteInfo(self, ee_domain) - ee_site_type = check_site.site_type - ee_site_webroot = check_site.site_path - if ee_site_webroot == 'deleted': - mark_webroot_deleted = True - if ee_site_type in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - ee_db_name = check_site.db_name - ee_db_user = check_site.db_user - ee_mysql_grant_host = self.app.config.get('mysql', 'grant-host') - if ee_db_name == 'deleted': - mark_db_deleted = True - if self.app.pargs.all: - self.app.pargs.db = True - self.app.pargs.files = True - else: - if self.app.pargs.all: - mark_db_deleted = True - self.app.pargs.files = True - - # Delete website database - if self.app.pargs.db: - if ee_db_name != 'deleted' and ee_db_name != '': - if not self.app.pargs.no_prompt: - ee_db_prompt = input('Are you sure, you want to delete' - ' database [y/N]: ') - else: - ee_db_prompt = 'Y' - mark_db_delete_prompt = True - - if ee_db_prompt == 'Y' or ee_db_prompt == 'y': - mark_db_delete_prompt = True - Log.info(self, "Deleting Database, {0}, user {1}" - .format(ee_db_name, ee_db_user)) - deleteDB(self, ee_db_name, ee_db_user, ee_mysql_grant_host, False) - updateSiteInfo(self, ee_domain, - db_name='deleted', - db_user='deleted', - db_password='deleted') - mark_db_deleted = True - Log.info(self, "Deleted Database successfully.") - else: - mark_db_deleted = True - Log.info(self, "Does not seems to have database for this site." - ) - - # Delete webroot - if self.app.pargs.files: - if ee_site_webroot != 'deleted': - if not self.app.pargs.no_prompt: - ee_web_prompt = input('Are you sure, you want to delete ' - 'webroot [y/N]: ') - else: - ee_web_prompt = 'Y' - mark_webroot_delete_prompt = True - - if ee_web_prompt == 'Y' or ee_web_prompt == 'y': - mark_webroot_delete_prompt = True - Log.info(self, "Deleting Webroot, {0}" - .format(ee_site_webroot)) - deleteWebRoot(self, ee_site_webroot) - updateSiteInfo(self, ee_domain, webroot='deleted') - mark_webroot_deleted = True - Log.info(self, "Deleted webroot successfully") - else: - mark_webroot_deleted = True - Log.info(self, "Webroot seems to be already deleted") - - if not self.app.pargs.force: - if (mark_webroot_deleted and mark_db_deleted): - # TODO Delete nginx conf - removeNginxConf(self, ee_domain) - deleteSiteInfo(self, ee_domain) - Log.info(self, "Deleted site {0}".format(ee_domain)) - # else: - # Log.error(self, " site {0} does not exists".format(ee_domain)) - else: - if (mark_db_delete_prompt or mark_webroot_delete_prompt or (mark_webroot_deleted and mark_db_deleted)): - # TODO Delete nginx conf - removeNginxConf(self, ee_domain) - deleteSiteInfo(self, ee_domain) - Log.info(self, "Deleted site {0}".format(ee_domain)) - - -class EESiteListController(CementBaseController): - class Meta: - label = 'list' - stacked_on = 'site' - stacked_type = 'nested' - description = 'List websites' - arguments = [ - (['--enabled'], - dict(help='List enabled websites', action='store_true')), - (['--disabled'], - dict(help="List disabled websites", action='store_true')), - ] - - @expose(help="Lists websites") - def default(self): - sites = getAllsites(self) - if not sites: - pass - - if self.app.pargs.enabled: - for site in sites: - if site.is_enabled: - Log.info(self, "{0}".format(site.sitename)) - elif self.app.pargs.disabled: - for site in sites: - if not site.is_enabled: - Log.info(self, "{0}".format(site.sitename)) - else: - for site in sites: - Log.info(self, "{0}".format(site.sitename)) - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EESiteController) - handler.register(EESiteCreateController) - handler.register(EESiteUpdateController) - handler.register(EESiteDeleteController) - handler.register(EESiteListController) - handler.register(EESiteEditController) - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_site_hook) diff --git a/ee/cli/plugins/site_functions.py b/ee/cli/plugins/site_functions.py deleted file mode 100644 index 149ed430b..000000000 --- a/ee/cli/plugins/site_functions.py +++ /dev/null @@ -1,1498 +0,0 @@ -from ee.cli.plugins.stack import EEStackController -from ee.core.fileutils import EEFileUtils -from ee.core.mysql import * -from ee.core.shellexec import * -from ee.core.sslutils import SSL -from ee.core.variables import EEVariables -from ee.cli.plugins.sitedb import * -from ee.core.aptget import EEAptGet -from ee.core.git import EEGit -from ee.core.logging import Log -from ee.core.sendmail import EESendMail -from ee.core.services import EEService -import subprocess -from subprocess import CalledProcessError -import os -import random -import string -import sys -import getpass -import glob -import re -import platform - - -class SiteError(Exception): - """Custom Exception Occured when setting up site""" - def __init__(self, message): - self.message = message - - def __str__(self): - return repr(self.message) - - -def pre_run_checks(self): - - # Check nginx configuration - Log.info(self, "Running pre-update checks, please wait...") - try: - Log.debug(self, "checking NGINX configuration ...") - FNULL = open('/dev/null', 'w') - ret = subprocess.check_call(["nginx", "-t"], stdout=FNULL, - stderr=subprocess.STDOUT) - except CalledProcessError as e: - Log.debug(self, "{0}".format(str(e))) - raise SiteError("nginx configuration check failed.") - - -def check_domain_exists(self, domain): - if getSiteInfo(self, domain): - return True - else: - return False - - -def setupdomain(self, data): - - #for debug purpose - # for key, value in data.items() : - # print (key, value) - - ee_domain_name = data['site_name'] - ee_site_webroot = data['webroot'] if 'webroot' in data.keys() else '' - - # Check if nginx configuration already exists - # if os.path.isfile('/etc/nginx/sites-available/{0}' - # .format(ee_domain_name)): - # raise SiteError("nginx configuration already exists for site") - - Log.info(self, "Setting up NGINX configuration \t", end='') - # write nginx config for file - try: - ee_site_nginx_conf = open('/etc/nginx/sites-available/{0}' - .format(ee_domain_name), encoding='utf-8', - mode='w') - if not data['php7']: - self.app.render((data), 'virtualconf.mustache', - out=ee_site_nginx_conf) - else: - self.app.render((data), 'virtualconf-php7.mustache', - out=ee_site_nginx_conf) - ee_site_nginx_conf.close() - except IOError as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("create nginx configuration failed for site") - except Exception as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("create nginx configuration failed for site") - finally: - # Check nginx -t and return status over it - try: - Log.debug(self, "Checking generated nginx conf, please wait...") - FNULL = open('/dev/null', 'w') - ret = subprocess.check_call(["nginx", "-t"], stdout=FNULL, - stderr=subprocess.STDOUT) - Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") - except CalledProcessError as e: - Log.debug(self, "{0}".format(str(e))) - Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" - + Log.OKBLUE + "]") - raise SiteError("created nginx configuration failed for site." - " check with `nginx -t`") - - - # create symbolic link for - EEFileUtils.create_symlink(self, ['/etc/nginx/sites-available/{0}' - .format(ee_domain_name), - '/etc/nginx/sites-enabled/{0}' - .format(ee_domain_name)]) - - if 'proxy' in data.keys() and data['proxy']: - return - - # Creating htdocs & logs directory - Log.info(self, "Setting up webroot \t\t", end='') - try: - if not os.path.exists('{0}/htdocs'.format(ee_site_webroot)): - os.makedirs('{0}/htdocs'.format(ee_site_webroot)) - if not os.path.exists('{0}/logs'.format(ee_site_webroot)): - os.makedirs('{0}/logs'.format(ee_site_webroot)) - if not os.path.exists('{0}/conf/nginx'.format(ee_site_webroot)): - os.makedirs('{0}/conf/nginx'.format(ee_site_webroot)) - - EEFileUtils.create_symlink(self, ['/var/log/nginx/{0}.access.log' - .format(ee_domain_name), - '{0}/logs/access.log' - .format(ee_site_webroot)]) - EEFileUtils.create_symlink(self, ['/var/log/nginx/{0}.error.log' - .format(ee_domain_name), - '{0}/logs/error.log' - .format(ee_site_webroot)]) - except Exception as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("setup webroot failed for site") - finally: - # TODO Check if directories are setup - if (os.path.exists('{0}/htdocs'.format(ee_site_webroot)) and - os.path.exists('{0}/logs'.format(ee_site_webroot))): - Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") - else: - Log.info(self, "[" + Log.ENDC + "Fail" + Log.OKBLUE + "]") - raise SiteError("setup webroot failed for site") - - -def setupdatabase(self, data): - ee_domain_name = data['site_name'] - ee_random = (''.join(random.sample(string.ascii_uppercase + - string.ascii_lowercase + string.digits, 15))) - ee_replace_dot = ee_domain_name.replace('.', '_') - prompt_dbname = self.app.config.get('mysql', 'db-name') - prompt_dbuser = self.app.config.get('mysql', 'db-user') - ee_mysql_grant_host = self.app.config.get('mysql', 'grant-host') - ee_db_name = '' - ee_db_username = '' - ee_db_password = '' - - if prompt_dbname == 'True' or prompt_dbname == 'true': - try: - ee_db_name = input('Enter the MySQL database name [{0}]: ' - .format(ee_replace_dot)) - except EOFError as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("Unable to input database name") - - if not ee_db_name: - ee_db_name = ee_replace_dot - - if prompt_dbuser == 'True' or prompt_dbuser == 'true': - try: - ee_db_username = input('Enter the MySQL database user name [{0}]: ' - .format(ee_replace_dot)) - ee_db_password = getpass.getpass(prompt='Enter the MySQL database' - ' password [{0}]: ' - .format(ee_random)) - except EOFError as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("Unable to input database credentials") - - if not ee_db_username: - ee_db_username = ee_replace_dot - if not ee_db_password: - ee_db_password = ee_random - - if len(ee_db_username) > 16: - Log.debug(self, 'Autofix MySQL username (ERROR 1470 (HY000)),' - ' please wait') - ee_db_username = (ee_db_name[0:6] + generate_random()) - - # create MySQL database - Log.info(self, "Setting up database\t\t", end='') - Log.debug(self, "Creating database {0}".format(ee_db_name)) - try: - if EEMysql.check_db_exists(self, ee_db_name): - Log.debug(self, "Database already exists, Updating DB_NAME .. ") - ee_db_name = (ee_db_name[0:6] + generate_random()) - ee_db_username = (ee_db_name[0:6] + generate_random()) - except MySQLConnectionError as e: - raise SiteError("MySQL Connectivity problem occured") - - try: - EEMysql.execute(self, "create database `{0}`" - .format(ee_db_name)) - except StatementExcecutionError as e: - Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") - raise SiteError("create database execution failed") - # Create MySQL User - Log.debug(self, "Creating user {0}".format(ee_db_username)) - Log.debug(self, "create user `{0}`@`{1}` identified by ''" - .format(ee_db_username, ee_mysql_grant_host)) - try: - EEMysql.execute(self, - "create user `{0}`@`{1}` identified by '{2}'" - .format(ee_db_username, ee_mysql_grant_host, - ee_db_password), log=False) - except StatementExcecutionError as e: - Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") - raise SiteError("creating user failed for database") - - # Grant permission - Log.debug(self, "Setting up user privileges") - try: - EEMysql.execute(self, - "grant all privileges on `{0}`.* to `{1}`@`{2}`" - .format(ee_db_name, - ee_db_username, ee_mysql_grant_host)) - except StatementExcecutionError as e: - Log.info(self, "[" + Log.ENDC + Log.FAIL + "Failed" + Log.OKBLUE + "]") - SiteError("grant privileges to user failed for database ") - - Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") - - data['ee_db_name'] = ee_db_name - data['ee_db_user'] = ee_db_username - data['ee_db_pass'] = ee_db_password - data['ee_db_host'] = EEVariables.ee_mysql_host - data['ee_mysql_grant_host'] = ee_mysql_grant_host - return(data) - - -def setupwordpress(self, data): - ee_domain_name = data['site_name'] - ee_site_webroot = data['webroot'] - prompt_wpprefix = self.app.config.get('wordpress', 'prefix') - ee_wp_user = self.app.config.get('wordpress', 'user') - ee_wp_pass = self.app.config.get('wordpress', 'password') - ee_wp_email = self.app.config.get('wordpress', 'email') - # Random characters - ee_random = (''.join(random.sample(string.ascii_uppercase + - string.ascii_lowercase + string.digits, 15))) - ee_wp_prefix = '' - # ee_wp_user = '' - # ee_wp_pass = '' - - if 'wp-user' in data.keys() and data['wp-user']: - ee_wp_user = data['wp-user'] - if 'wp-email' in data.keys() and data['wp-email']: - ee_wp_email = data['wp-email'] - if 'wp-pass' in data.keys() and data['wp-pass']: - ee_wp_pass = data['wp-pass'] - - Log.info(self, "Downloading WordPress \t\t", end='') - EEFileUtils.chdir(self, '{0}/htdocs/'.format(ee_site_webroot)) - try: - if EEShellExec.cmd_exec(self, "wp --allow-root core" - " download"): - pass - else: - Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") - raise SiteError("download WordPress core failed") - except CommandExecutionError as e: - Log.info(self, "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") - raise SiteError(self, "download WordPress core failed") - - Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") - - if not (data['ee_db_name'] and data['ee_db_user'] and data['ee_db_pass']): - data = setupdatabase(self, data) - if prompt_wpprefix == 'True' or prompt_wpprefix == 'true': - try: - ee_wp_prefix = input('Enter the WordPress table prefix [wp_]: ') - while not re.match('^[A-Za-z0-9_]*$', ee_wp_prefix): - Log.warn(self, "table prefix can only " - "contain numbers, letters, and underscores") - ee_wp_prefix = input('Enter the WordPress table prefix [wp_]: ' - ) - except EOFError as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("input table prefix failed") - - if not ee_wp_prefix: - ee_wp_prefix = 'wp_' - - # Modify wp-config.php & move outside the webroot - - EEFileUtils.chdir(self, '{0}/htdocs/'.format(ee_site_webroot)) - Log.debug(self, "Setting up wp-config file") - if not data['multisite']: - Log.debug(self, "Generating wp-config for WordPress Single site") - Log.debug(self, "bash -c \"php {0} --allow-root " - .format(EEVariables.ee_wpcli_path) - + "core config " - + "--dbname=\'{0}\' --dbprefix=\'{1}\' --dbuser=\'{2}\' " - "--dbhost=\'{3}\' " - .format(data['ee_db_name'], ee_wp_prefix, - data['ee_db_user'], data['ee_db_host']) - + "--dbpass= " - "--extra-php< {1}/{0}.sql" - .format(data['ee_db_name'], - backup_path)): - Log.info(self, - "[" + Log.ENDC + Log.FAIL + "Fail" + Log.OKBLUE + "]") - raise SiteError("mysqldump failed to backup database") - except CommandExecutionError as e: - Log.info(self, "[" + Log.ENDC + "Fail" + Log.OKBLUE + "]") - raise SiteError("mysqldump failed to backup database") - Log.info(self, "[" + Log.ENDC + "Done" + Log.OKBLUE + "]") - # move wp-config.php/ee-config.php to backup - if data['currsitetype'] in ['mysql', 'proxy']: - if data['php7'] is True and not data['wp']: - EEFileUtils.copyfile(self, configfiles[0], backup_path) - else: - EEFileUtils.mvfile(self, configfiles[0], backup_path) - else: - EEFileUtils.copyfile(self, configfiles[0], backup_path) - - -def site_package_check(self, stype): - apt_packages = [] - packages = [] - stack = EEStackController() - stack.app = self.app - if stype in ['html', 'proxy', 'php', 'mysql', 'wp', 'wpsubdir', - 'wpsubdomain', 'php7']: - Log.debug(self, "Setting apt_packages variable for Nginx") - - # Check if server has nginx-custom package - if not (EEAptGet.is_installed(self, 'nginx-custom') or EEAptGet.is_installed(self, 'nginx-mainline')): - # check if Server has nginx-plus installed - if EEAptGet.is_installed(self, 'nginx-plus'): - # do something - # do post nginx installation configuration - Log.info(self, "NGINX PLUS Detected ...") - apt = ["nginx-plus"] + EEVariables.ee_nginx - #apt_packages = apt_packages + EEVariables.ee_nginx - stack.post_pref(apt, packages) - elif EEAptGet.is_installed(self, 'nginx'): - Log.info(self, "EasyEngine detected a previously installed Nginx package. " - "It may or may not have required modules. " - "\nIf you need help, please create an issue at https://github.com/EasyEngine/easyengine/issues/ \n") - apt = ["nginx"] + EEVariables.ee_nginx - #apt_packages = apt_packages + EEVariables.ee_nginx - stack.post_pref(apt, packages) - else: - apt_packages = apt_packages + EEVariables.ee_nginx - else: - # Fix for Nginx white screen death - if not EEFileUtils.grep(self, '/etc/nginx/fastcgi_params', - 'SCRIPT_FILENAME'): - with open('/etc/nginx/fastcgi_params', encoding='utf-8', - mode='a') as ee_nginx: - ee_nginx.write('fastcgi_param \tSCRIPT_FILENAME ' - '\t$request_filename;\n') - - if self.app.pargs.php and self.app.pargs.php7: - Log.error(self,"INVALID OPTION: PHP 7.0 provided with PHP 5.0") - - if not self.app.pargs.php7 and stype in ['php', 'mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - Log.debug(self, "Setting apt_packages variable for PHP") - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if not EEAptGet.is_installed(self, 'php5.6-fpm'): - apt_packages = apt_packages + EEVariables.ee_php5_6 + EEVariables.ee_php_extra - else: - if not EEAptGet.is_installed(self, 'php5-fpm'): - apt_packages = apt_packages + EEVariables.ee_php - - if self.app.pargs.php7 and stype in [ 'mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - Log.debug(self, "Setting apt_packages variable for PHP 5.6") - if not EEAptGet.is_installed(self, 'php5.6-fpm'): - apt_packages = apt_packages + EEVariables.ee_php5_6 - Log.debug(self, "Setting apt_packages variable for PHP 7.0") - if not EEAptGet.is_installed(self, 'php7.0-fpm'): - apt_packages = apt_packages + EEVariables.ee_php7_0 + EEVariables.ee_php_extra - else: - if EEVariables.ee_platform_codename == 'wheezy': - Log.warn(self, "PHP 7.0 not available for your system.") - Log.info(self,"Creating site with PHP 5.6") - if not EEAptGet.is_installed(self, 'php5-fpm'): - Log.info(self, "Setting apt_packages variable for PHP") - Log.debug(self, "Setting apt_packages variable for PHP") - apt_packages = apt_packages + EEVariables.ee_php - else: - Log.debug(self, "Setting apt_packages variable for PHP 7.0") - if not EEAptGet.is_installed(self, 'php7.0-fpm'): - apt_packages = apt_packages + EEVariables.ee_php7_0 - - if stype in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - Log.debug(self, "Setting apt_packages variable for MySQL") - if not EEShellExec.cmd_exec(self, "mysqladmin ping"): - apt_packages = apt_packages + EEVariables.ee_mysql - packages = packages + [["https://raw.githubusercontent.com/" - "major/MySQLTuner-perl/master/" - "mysqltuner.pl", "/usr/bin/mysqltuner", - "MySQLTuner"]] - - if stype in ['php', 'mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - Log.debug(self, "Setting apt_packages variable for Postfix") - if not EEAptGet.is_installed(self, 'postfix'): - apt_packages = apt_packages + EEVariables.ee_postfix - - if stype in ['wp', 'wpsubdir', 'wpsubdomain']: - Log.debug(self, "Setting packages variable for WP-CLI") - if not EEShellExec.cmd_exec(self, "which wp"): - packages = packages + [["https://github.com/wp-cli/wp-cli/" - "releases/download/v{0}/" - "wp-cli-{0}.phar" - .format(EEVariables.ee_wp_cli), - "/usr/bin/wp", "WP-CLI"]] - if self.app.pargs.wpredis: - Log.debug(self, "Setting apt_packages variable for redis") - if not EEAptGet.is_installed(self, 'redis-server'): - apt_packages = apt_packages + EEVariables.ee_redis - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis.conf")): - - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis.conf') - ee_nginx = open('/etc/nginx/common/redis.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis-hhvm.conf")): - - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis-hhvm.conf') - ee_nginx = open('/etc/nginx/common/redis-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/" - "upstream.conf", - "redis"): - with open("/etc/nginx/conf.d/upstream.conf", - "a") as redis_file: - redis_file.write("upstream redis {\n" - " server 127.0.0.1:6379;\n" - " keepalive 10;\n}") - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/conf.d/redis.conf")): - with open("/etc/nginx/conf.d/redis.conf", "a") as redis_file: - redis_file.write("# Log format Settings\n" - "log_format rt_cache_redis '$remote_addr $upstream_response_time $srcache_fetch_status [$time_local] '\n" - "'$http_host \"$request\" $status $body_bytes_sent '\n" - "'\"$http_referer\" \"$http_user_agent\"';\n") - - if self.app.pargs.hhvm: - if platform.architecture()[0] is '32bit': - Log.error(self, "HHVM is not supported by 32bit system") - Log.debug(self, "Setting apt_packages variable for HHVM") - if not EEAptGet.is_installed(self, 'hhvm'): - apt_packages = apt_packages + EEVariables.ee_hhvm - - if os.path.isdir("/etc/nginx/common") and (not - os.path.isfile("/etc/nginx/common/php-hhvm.conf")): - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/php-hhvm.conf') - ee_nginx = open('/etc/nginx/common/php-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/w3tc-hhvm.conf') - ee_nginx = open('/etc/nginx/common/w3tc-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'w3tc-hhvm.mustache', out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpfc-hhvm.conf') - ee_nginx = open('/etc/nginx/common/wpfc-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpfc-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpsc-hhvm.conf') - ee_nginx = open('/etc/nginx/common/wpsc-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpsc-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/upstream.conf", - "hhvm"): - with open("/etc/nginx/conf.d/upstream.conf", "a") as hhvm_file: - hhvm_file.write("upstream hhvm {\nserver 127.0.0.1:8000;\n" - "server 127.0.0.1:9000 backup;\n}\n") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'wheezy' or EEVariables.ee_platform_codename == 'precise'): - Log.error(self,"PHP 7.0 is not supported in your Platform") - - Log.debug(self, "Setting apt_packages variable for PHP 7.0") - if not EEAptGet.is_installed(self, 'php7.0-fpm'): - apt_packages = apt_packages + EEVariables.ee_php7_0 + EEVariables.ee_php_extra - - if os.path.isdir("/etc/nginx/common") and (not - os.path.isfile("/etc/nginx/common/php7.conf")): - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/locations-php7.conf') - ee_nginx = open('/etc/nginx/common/locations-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'locations-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/php7.conf') - ee_nginx = open('/etc/nginx/common/php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/w3tc-php7.conf') - ee_nginx = open('/etc/nginx/common/w3tc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'w3tc-php7.mustache', out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpcommon-php7.conf') - ee_nginx = open('/etc/nginx/common/wpcommon-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpcommon-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpfc-php7.conf') - ee_nginx = open('/etc/nginx/common/wpfc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpfc-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpsc-php7.conf') - ee_nginx = open('/etc/nginx/common/wpsc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpsc-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis-php7.conf")): - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis-php7.conf') - ee_nginx = open('/etc/nginx/common/redis-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/upstream.conf", - "php7"): - with open("/etc/nginx/conf.d/upstream.conf", "a") as php_file: - php_file.write("upstream php7 {\nserver 127.0.0.1:9070;\n}\n" - "upstream debug7 {\nserver 127.0.0.1:9170;\n}\n") - - - # Check if Nginx is allready installed and Pagespeed config there or not - # If not then copy pagespeed config -# if self.app.pargs.pagespeed: -# if (os.path.isfile('/etc/nginx/nginx.conf') and -# (not os.path.isfile('/etc/nginx/conf.d/pagespeed.conf'))): - # Pagespeed configuration -# data = dict() -# Log.debug(self, 'Writting the Pagespeed Global ' -# 'configuration to file /etc/nginx/conf.d/' -# 'pagespeed.conf') -# ee_nginx = open('/etc/nginx/conf.d/pagespeed.conf', -# encoding='utf-8', mode='w') -# self.app.render((data), 'pagespeed-global.mustache', -# out=ee_nginx) -# ee_nginx.close() - - return(stack.install(apt_packages=apt_packages, packages=packages, - disp_msg=False)) - - -def updatewpuserpassword(self, ee_domain, ee_site_webroot): - - ee_wp_user = '' - ee_wp_pass = '' - EEFileUtils.chdir(self, '{0}/htdocs/'.format(ee_site_webroot)) - - # Check if ee_domain is wordpress install - try: - is_wp = EEShellExec.cmd_exec(self, "wp --allow-root core" - " version") - except CommandExecutionError as e: - raise SiteError("is WordPress site? check command failed ") - - # Exit if ee_domain is not wordpress install - if not is_wp: - Log.error(self, "{0} does not seem to be a WordPress site" - .format(ee_domain)) - - try: - ee_wp_user = input("Provide WordPress user name [admin]: ") - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "\nCould not update password") - - if ee_wp_user == "?": - Log.info(self, "Fetching WordPress user list") - try: - EEShellExec.cmd_exec(self, "wp --allow-root user list " - "--fields=user_login | grep -v user_login") - except CommandExecutionError as e: - raise SiteError("fetch wp userlist command failed") - - if not ee_wp_user: - ee_wp_user = 'admin' - - try: - is_user_exist = EEShellExec.cmd_exec(self, "wp --allow-root user list " - "--fields=user_login | grep {0}$ " - .format(ee_wp_user)) - except CommandExecutionError as e: - raise SiteError("if wp user exists check command failed") - - if is_user_exist: - try: - ee_wp_pass = getpass.getpass(prompt="Provide password for " - "{0} user: " - .format(ee_wp_user)) - - while not ee_wp_pass: - ee_wp_pass = getpass.getpass(prompt="Provide password for " - "{0} user: " - .format(ee_wp_user)) - except Exception as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("failed to read password input ") - - try: - EEShellExec.cmd_exec(self, "wp --allow-root user update {0}" - " --user_pass={1}" - .format(ee_wp_user, ee_wp_pass)) - except CommandExecutionError as e: - raise SiteError("wp user password update command failed") - Log.info(self, "Password updated successfully") - - else: - Log.error(self, "Invalid WordPress user {0} for {1}." - .format(ee_wp_user, ee_domain)) - - -def display_cache_settings(self, data): - if data['wpsc']: - if data['multisite']: - Log.info(self, "Configure WPSC:" - "\t\thttp://{0}/wp-admin/network/settings.php?" - "page=wpsupercache" - .format(data['site_name'])) - else: - Log.info(self, "Configure WPSC:" - "\t\thttp://{0}/wp-admin/options-general.php?" - "page=wpsupercache" - .format(data['site_name'])) - - if data['wpredis']: - if data['multisite']: - Log.info(self, "Configure redis-cache:" - "\thttp://{0}/wp-admin/network/settings.php?" - "page=redis-cache".format(data['site_name'])) - else: - Log.info(self, "Configure redis-cache:" - "\thttp://{0}/wp-admin/options-general.php?" - "page=redis-cache".format(data['site_name'])) - Log.info(self, "Object Cache:\t\tEnable") - - if data['wpfc'] or data['w3tc']: - if data['multisite']: - Log.info(self, "Configure W3TC:" - "\t\thttp://{0}/wp-admin/network/admin.php?" - "page=w3tc_general".format(data['site_name'])) - else: - Log.info(self, "Configure W3TC:" - "\t\thttp://{0}/wp-admin/admin.php?" - "page=w3tc_general".format(data['site_name'])) - - if data['wpfc']: - Log.info(self, "Page Cache:\t\tDisable") - elif data['w3tc']: - Log.info(self, "Page Cache:\t\tDisk Enhanced") - Log.info(self, "Database Cache:\t\tMemcached") - Log.info(self, "Object Cache:\t\tMemcached") - Log.info(self, "Browser Cache:\t\tDisable") - - -def logwatch(self, logfiles): - import zlib - import base64 - import time - from ee.core import logwatch - - def callback(filename, lines): - for line in lines: - if line.find(':::') == -1: - print(line) - else: - data = line.split(':::') - try: - print(data[0], data[1], - zlib.decompress(base64.decodestring(data[2]))) - except Exception as e: - Log.info(time.time(), - 'caught exception rendering a new log line in %s' - % filename) - - l = logwatch.LogWatcher(logfiles, callback) - l.loop() - - -def detSitePar(opts): - """ - Takes dictionary of parsed arguments - 1.returns sitetype and cachetype - 2. raises RuntimeError when wrong combination is used like - "--wp --wpsubdir" or "--html --wp" - """ - sitetype, cachetype = '', '' - typelist = list() - cachelist = list() - for key, val in opts.items(): - if val and key in ['html', 'php', 'mysql', 'wp', - 'wpsubdir', 'wpsubdomain','php7']: - typelist.append(key) - elif val and key in ['wpfc', 'wpsc', 'w3tc', 'wpredis']: - cachelist.append(key) - - if len(typelist) > 1 or len(cachelist) > 1: - if len(cachelist) > 1: - raise RuntimeError("Could not determine cache type.Multiple cache parameter entered") - elif False not in [x in ('php','mysql','html') for x in typelist]: - sitetype = 'mysql' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('php7','mysql','html') for x in typelist]: - sitetype = 'mysql' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('php','mysql') for x in typelist]: - sitetype = 'mysql' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('php7','mysql') for x in typelist]: - sitetype = 'mysql' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('html','mysql') for x in typelist]: - sitetype = 'mysql' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('php','html') for x in typelist]: - sitetype = 'php' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('php7','html') for x in typelist]: - sitetype = 'php7' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('wp','wpsubdir') for x in typelist]: - sitetype = 'wpsubdir' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('wp','wpsubdomain') for x in typelist]: - sitetype = 'wpsubdomain' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('wp','php7') for x in typelist]: - sitetype = 'wp' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('wpsubdir','php7') for x in typelist]: - sitetype = 'wpsubdir' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - elif False not in [x in ('wpsubdomain','php7') for x in typelist]: - sitetype = 'wpsubdomain' - if not cachelist: - cachetype = 'basic' - else: - cachetype = cachelist[0] - else: - raise RuntimeError("could not determine site and cache type") - else: - if not typelist and not cachelist: - sitetype = None - cachetype = None - elif (not typelist or "php7" in typelist) and cachelist: - sitetype = 'wp' - cachetype = cachelist[0] - elif typelist and (not cachelist): - sitetype = typelist[0] - cachetype = 'basic' - else: - sitetype = typelist[0] - cachetype = cachelist[0] - - return (sitetype, cachetype) - - -def generate_random(): - ee_random10 = (''.join(random.sample(string.ascii_uppercase + - string.ascii_lowercase + string.digits, 10))) - return ee_random10 - - -def deleteDB(self, dbname, dbuser, dbhost, exit=True): - try: - # Check if Database exists - try: - if EEMysql.check_db_exists(self, dbname): - # Drop database if exists - Log.debug(self, "dropping database `{0}`".format(dbname)) - EEMysql.execute(self, - "drop database `{0}`".format(dbname), - errormsg='Unable to drop database {0}' - .format(dbname)) - except StatementExcecutionError as e: - Log.debug(self, "drop database failed") - Log.info(self, "Database {0} not dropped".format(dbname)) - - except MySQLConnectionError as e: - Log.debug(self, "Mysql Connection problem occured") - - if dbuser != 'root': - Log.debug(self, "dropping user `{0}`".format(dbuser)) - try: - EEMysql.execute(self, - "drop user `{0}`@`{1}`" - .format(dbuser, dbhost)) - except StatementExcecutionError as e: - Log.debug(self, "drop database user failed") - Log.info(self, "Database {0} not dropped".format(dbuser)) - try: - EEMysql.execute(self, "flush privileges") - except StatementExcecutionError as e: - Log.debug(self, "drop database failed") - Log.info(self, "Database {0} not dropped".format(dbname)) - except Exception as e: - Log.error(self, "Error occured while deleting database", exit) - - -def deleteWebRoot(self, webroot): - # do some preprocessing before proceeding - webroot = webroot.strip() - if (webroot == "/var/www/" or webroot == "/var/www" - or webroot == "/var/www/.." or webroot == "/var/www/."): - Log.debug(self, "Tried to remove {0}, but didn't remove it" - .format(webroot)) - return False - - if os.path.isdir(webroot): - Log.debug(self, "Removing {0}".format(webroot)) - EEFileUtils.rm(self, webroot) - return True - else: - Log.debug(self, "{0} does not exist".format(webroot)) - return False - - -def removeNginxConf(self, domain): - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(domain)): - Log.debug(self, "Removing Nginx configuration") - EEFileUtils.rm(self, '/etc/nginx/sites-enabled/{0}' - .format(domain)) - EEFileUtils.rm(self, '/etc/nginx/sites-available/{0}' - .format(domain)) - EEService.reload_service(self, 'nginx') - EEGit.add(self, ["/etc/nginx"], - msg="Deleted {0} " - .format(domain)) - - -def doCleanupAction(self, domain='', webroot='', dbname='', dbuser='', - dbhost=''): - """ - Removes the nginx configuration and database for the domain provided. - doCleanupAction(self, domain='sitename', webroot='', - dbname='', dbuser='', dbhost='') - """ - if domain: - if os.path.isfile('/etc/nginx/sites-available/{0}' - .format(domain)): - removeNginxConf(self, domain) - if webroot: - deleteWebRoot(self, webroot) - - if dbname: - if not dbuser: - raise SiteError("dbuser not provided") - if not dbhost: - raise SiteError("dbhost not provided") - deleteDB(self, dbname, dbuser, dbhost) - -def cloneLetsEncrypt(self): - letsencrypt_repo = "https://github.com/letsencrypt/letsencrypt" - if not os.path.isdir("/opt"): - EEFileUtils.mkdir(self,"/opt") - try: - Log.info(self, "Downloading {0:20}".format("LetsEncrypt"), end=' ') - EEFileUtils.chdir(self, '/opt/') - EEShellExec.cmd_exec(self, "git clone {0}".format(letsencrypt_repo)) - Log.info(self, "{0}".format("[" + Log.ENDC + "Done" - + Log.OKBLUE + "]")) - return True - except Exception as e: - Log.debug(self, "[{err}]".format(err=str(e.reason))) - Log.error(self, "Unable to download file, LetsEncrypt") - return False - -def setupLetsEncrypt(self, ee_domain_name): - ee_wp_email = EEVariables.ee_email - while not ee_wp_email: - try: - ee_wp_email = input('Enter WordPress email: ') - except EOFError as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("input WordPress username failed") - - if not os.path.isdir("/opt/letsencrypt"): - cloneLetsEncrypt(self) - EEFileUtils.chdir(self, '/opt/letsencrypt') - EEShellExec.cmd_exec(self, "git pull") - - if os.path.isfile("/etc/letsencrypt/renewal/{0}.conf".format(ee_domain_name)): - Log.debug(self, "LetsEncrypt SSL Certificate found for the domain {0}" - .format(ee_domain_name)) - ssl= archivedCertificateHandle(self,ee_domain_name,ee_wp_email) - else: - Log.warn(self,"Please Wait while we fetch SSL Certificate for your site.\nIt may take time depending upon network.") - ssl = EEShellExec.cmd_exec(self, "./letsencrypt-auto certonly --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " - .format(ee_domain_name) - + "--email {0} --text --agree-tos".format(ee_wp_email)) - if ssl: - Log.info(self, "Let's Encrypt successfully setup for your site") - Log.info(self, "Your certificate and chain have been saved at " - "/etc/letsencrypt/live/{0}/fullchain.pem".format(ee_domain_name)) - Log.info(self, "Configuring Nginx SSL configuration") - - try: - Log.info(self, "Adding /var/www/{0}/conf/nginx/ssl.conf".format(ee_domain_name)) - - sslconf = open("/var/www/{0}/conf/nginx/ssl.conf" - .format(ee_domain_name), - encoding='utf-8', mode='w') - sslconf.write("listen 443 ssl http2;\n" - "ssl on;\n" - "ssl_certificate /etc/letsencrypt/live/{0}/fullchain.pem;\n" - "ssl_certificate_key /etc/letsencrypt/live/{0}/privkey.pem;\n" - .format(ee_domain_name)) - sslconf.close() - # updateSiteInfo(self, ee_domain_name, ssl=True) - - EEGit.add(self, ["/etc/letsencrypt"], - msg="Adding letsencrypt folder") - - except IOError as e: - Log.debug(self, str(e)) - Log.debug(self, "Error occured while generating " - "ssl.conf") - else: - Log.error(self, "Unable to setup, Let\'s Encrypt", False) - Log.error(self, "Please make sure that your site is pointed to \n" - "same server on which you are running Let\'s Encrypt Client " - "\n to allow it to verify the site automatically.") - -def renewLetsEncrypt(self, ee_domain_name): - - ee_wp_email = EEVariables.ee_email - while not ee_wp_email: - try: - ee_wp_email = input('Enter email address: ') - except EOFError as e: - Log.debug(self, "{0}".format(e)) - raise SiteError("Input WordPress email failed") - - if not os.path.isdir("/opt/letsencrypt"): - cloneLetsEncrypt(self) - EEFileUtils.chdir(self, '/opt/letsencrypt') - EEShellExec.cmd_exec(self, "git pull") - - Log.info(self, "Renewing SSl cert for https://{0}".format(ee_domain_name)) - - ssl = EEShellExec.cmd_exec(self, "./letsencrypt-auto --renew-by-default certonly --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " - .format(ee_domain_name) - + "--email {0} --text --agree-tos".format(ee_wp_email)) - mail_list = '' - if not ssl: - Log.error(self,"ERROR : Cannot RENEW SSL cert !",False) - if (SSL.getExpirationDays(self,ee_domain_name)>0): - Log.error(self, "Your current cert will expire within " + str(SSL.getExpirationDays(self,ee_domain_name)) + " days.",False) - else: - Log.error(self, "Your current cert already EXPIRED !",False) - - EESendMail("easyengine@{0}".format(ee_domain_name), ee_wp_email, "[FAIL] SSL cert renewal {0}".format(ee_domain_name), - "Hey Hi,\n\nSSL Certificate renewal for https://{0} was unsuccessful.".format(ee_domain_name) + - "\nPlease check easyengine log for reason. Your SSL Expiry date : " + - str(SSL.getExpirationDate(self,ee_domain_name)) + - "\n\nFor support visit https://easyengine.io/support/ .\n\nYour's faithfully,\nEasyEngine",files=mail_list, - port=25, isTls=False) - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - EEGit.add(self, ["/etc/letsencrypt"], - msg="Adding letsencrypt folder") - EESendMail("easyengine@{0}".format(ee_domain_name), ee_wp_email, "[SUCCESS] SSL cert renewal {0}".format(ee_domain_name), - "Hey Hi,\n\nYour SSL Certificate has been renewed for https://{0} .".format(ee_domain_name) + - "\nYour SSL will Expire on : " + - str(SSL.getExpirationDate(self,ee_domain_name)) + - "\n\nYour's faithfully,\nEasyEngine",files=mail_list, - port=25, isTls=False) - -#redirect= False to disable https redirection -def httpsRedirect(self,ee_domain_name,redirect=True): - if redirect: - if os.path.isfile("/etc/nginx/conf.d/force-ssl-{0}.conf.disabled".format(ee_domain_name)): - EEFileUtils.mvfile(self, "/etc/nginx/conf.d/force-ssl-{0}.conf.disabled".format(ee_domain_name), - "/etc/nginx/conf.d/force-ssl-{0}.conf".format(ee_domain_name)) - else: - try: - Log.info(self, "Adding /etc/nginx/conf.d/force-ssl-{0}.conf".format(ee_domain_name)) - - sslconf = open("/etc/nginx/conf.d/force-ssl-{0}.conf" - .format(ee_domain_name), - encoding='utf-8', mode='w') - sslconf.write("server {\n" - "\tlisten 80;\n" + - "\tserver_name www.{0} {0};\n".format(ee_domain_name) + - "\treturn 301 https://{0}".format(ee_domain_name)+"$request_uri;\n}" ) - sslconf.close() - # Nginx Configation into GIT - except IOError as e: - Log.debug(self, str(e)) - Log.debug(self, "Error occured while generating " - "/etc/nginx/conf.d/force-ssl-{0}.conf".format(ee_domain_name)) - - Log.info(self, "Added HTTPS Force Redirection for Site " - " http://{0}".format(ee_domain_name)) - EEGit.add(self, - ["/etc/nginx"], msg="Adding /etc/nginx/conf.d/force-ssl-{0}.conf".format(ee_domain_name)) - else: - if os.path.isfile("/etc/nginx/conf.d/force-ssl-{0}.conf".format(ee_domain_name)): - EEFileUtils.mvfile(self, "/etc/nginx/conf.d/force-ssl-{0}.conf".format(ee_domain_name), - "/etc/nginx/conf.d/force-ssl-{0}.conf.disabled".format(ee_domain_name)) - Log.info(self, "Disabled HTTPS Force Redirection for Site " - " http://{0}".format(ee_domain_name)) - -def archivedCertificateHandle(self,domain,ee_wp_email): - Log.warn(self,"You already have an existing certificate for the domain requested.\n" - "(ref: /etc/letsencrypt/renewal/{0}.conf)".format(domain) + - "\nPlease select an option from below?" - "\n\t1: Reinstall existing certificate" - "\n\t2: Keep the existing certificate for now" - "\n\t3: Renew & replace the certificate (limit ~5 per 7 days)" - "") - check_prompt = input("\nType the appropriate number [1-3] or any other key to cancel: ") - if not os.path.isfile("/etc/letsencrypt/live/{0}/cert.pem".format(domain)): - Log.error(self,"/etc/letsencrypt/live/{0}/cert.pem file is missing.".format(domain)) - if check_prompt == "1": - Log.info(self,"Please Wait while we reinstall SSL Certificate for your site.\nIt may take time depending upon network.") - ssl = EEShellExec.cmd_exec(self, "./letsencrypt-auto certonly --reinstall --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " - .format(domain) - + "--email {0} --text --agree-tos".format(ee_wp_email)) - elif check_prompt == "2" : - Log.info(self,"Using Existing Certificate files") - if not (os.path.isfile("/etc/letsencrypt/live/{0}/fullchain.pem".format(domain)) or - os.path.isfile("/etc/letsencrypt/live/{0}/privkey.pem".format(domain))): - Log.error(self,"Certificate files not found. Skipping.\n" - "Please check if following file exist\n\t/etc/letsencrypt/live/{0}/fullchain.pem\n\t" - "/etc/letsencrypt/live/{0}/privkey.pem".format(domain)) - ssl = True - - elif check_prompt == "3": - Log.info(self,"Please Wait while we renew SSL Certificate for your site.\nIt may take time depending upon network.") - ssl = EEShellExec.cmd_exec(self, "./letsencrypt-auto --renew-by-default certonly --webroot -w /var/www/{0}/htdocs/ -d {0} -d www.{0} " - .format(domain) - + "--email {0} --text --agree-tos".format(ee_wp_email)) - else: - Log.error(self,"Operation cancelled by user.") - - if os.path.isfile("{0}/conf/nginx/ssl.conf" - .format(domain)): - Log.info(self, "Existing ssl.conf . Backing it up ..") - EEFileUtils.mvfile(self, "/var/www/{0}/conf/nginx/ssl.conf" - .format(domain), - '/var/www/{0}/conf/nginx/ssl.conf.bak' - .format(domain)) - - return ssl diff --git a/ee/cli/plugins/sitedb.py b/ee/cli/plugins/sitedb.py deleted file mode 100644 index b3cca14e0..000000000 --- a/ee/cli/plugins/sitedb.py +++ /dev/null @@ -1,129 +0,0 @@ -from sqlalchemy import Column, DateTime, String, Integer, Boolean -from sqlalchemy import ForeignKey, func -from sqlalchemy.orm import relationship, backref -from sqlalchemy.ext.declarative import declarative_base -from ee.core.logging import Log -from ee.core.database import db_session -from ee.cli.plugins.models import SiteDB -import sys -import glob - - -def addNewSite(self, site, stype, cache, path, - enabled=True, ssl=False, fs='ext4', db='mysql', - db_name=None, db_user=None, db_password=None, - db_host='localhost', hhvm=0, pagespeed=0, php_version='5.5'): - """ - Add New Site record information into ee database. - """ - try: - newRec = SiteDB(site, stype, cache, path, enabled, ssl, fs, db, - db_name, db_user, db_password, db_host, hhvm, - pagespeed, php_version) - db_session.add(newRec) - db_session.commit() - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to add site to database") - - -def getSiteInfo(self, site): - """ - Retrieves site record from ee databse - """ - try: - q = SiteDB.query.filter(SiteDB.sitename == site).first() - return q - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to query database for site info") - - -def updateSiteInfo(self, site, stype='', cache='', webroot='', - enabled=True, ssl=False, fs='', db='', db_name=None, - db_user=None, db_password=None, db_host=None, hhvm=None, - pagespeed=None, php_version=''): - """updates site record in database""" - try: - q = SiteDB.query.filter(SiteDB.sitename == site).first() - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to query database for site info") - - if not q: - Log.error(self, "{0} does not exist in database".format(site)) - - # Check if new record matches old if not then only update database - if stype and q.site_type != stype: - q.site_type = stype - - if cache and q.cache_type != cache: - q.cache_type = cache - - if q.is_enabled != enabled: - q.is_enabled = enabled - - if q.is_ssl != ssl: - q.is_ssl = ssl - - if db_name and q.db_name != db_name: - q.db_name = db_name - - if db_user and q.db_user != db_user: - q.db_user = db_user - - if db_user and q.db_password != db_password: - q.db_password = db_password - - if db_host and q.db_host != db_host: - q.db_host = db_host - - if webroot and q.site_path != webroot: - q.site_path = webroot - - if (hhvm is not None) and (q.is_hhvm is not hhvm): - q.is_hhvm = hhvm - - if (pagespeed is not None) and (q.is_pagespeed is not pagespeed): - q.is_pagespeed = pagespeed - - if php_version and q.php_version != php_version: - q.php_version = php_version - - try: - q.created_on = func.now() - db_session.commit() - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to update site info in application database.") - - -def deleteSiteInfo(self, site): - """Delete site record in database""" - try: - q = SiteDB.query.filter(SiteDB.sitename == site).first() - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to query database") - - if not q: - Log.error(self, "{0} does not exist in database".format(site)) - - try: - db_session.delete(q) - db_session.commit() - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to delete site from application database.") - - -def getAllsites(self): - """ - 1. returns all records from ee database - """ - try: - q = SiteDB.query.all() - return q - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to query database") diff --git a/ee/cli/plugins/stack.py b/ee/cli/plugins/stack.py deleted file mode 100644 index 6fa39a0d2..000000000 --- a/ee/cli/plugins/stack.py +++ /dev/null @@ -1,2794 +0,0 @@ -"""Stack Plugin for EasyEngine.""" - -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.cli.plugins.site_functions import * -from ee.core.variables import EEVariables -from ee.core.aptget import EEAptGet -from ee.core.download import EEDownload -from ee.core.shellexec import EEShellExec, CommandExecutionError -from ee.core.fileutils import EEFileUtils -from ee.core.apt_repo import EERepo -from ee.core.extract import EEExtract -from ee.core.mysql import EEMysql -from ee.core.addswap import EESwap -from ee.core.git import EEGit -from ee.core.checkfqdn import check_fqdn -from pynginxconfig import NginxConfig -from ee.core.services import EEService -from ee.core.variables import EEVariables -import random -import string -import configparser -import time -import shutil -import os -import pwd -import grp -import codecs -import platform -from ee.cli.plugins.stack_services import EEStackStatusController -from ee.cli.plugins.stack_migrate import EEStackMigrateController -from ee.cli.plugins.stack_upgrade import EEStackUpgradeController -from ee.core.logging import Log -from ee.cli.plugins.sitedb import * - - -def ee_stack_hook(app): - # do something with the ``app`` object here. - pass - - -class EEStackController(CementBaseController): - class Meta: - label = 'stack' - stacked_on = 'base' - stacked_type = 'nested' - description = 'Stack command manages stack operations' - arguments = [ - (['--all'], - dict(help='Install all stack', action='store_true')), - (['--web'], - dict(help='Install web stack', action='store_true')), - (['--admin'], - dict(help='Install admin tools stack', action='store_true')), - (['--mail'], - dict(help='Install mail server stack', action='store_true')), - (['--mailscanner'], - dict(help='Install mail scanner stack', action='store_true')), - (['--nginx'], - dict(help='Install Nginx stack', action='store_true')), -# (['--nginxmainline'], -# dict(help='Install Nginx mainline stack', action='store_true')), - (['--php'], - dict(help='Install PHP stack', action='store_true')), - (['--php7'], - dict(help='Install PHP 7.0 stack', action='store_true')), - (['--mysql'], - dict(help='Install MySQL stack', action='store_true')), - (['--hhvm'], - dict(help='Install HHVM stack', action='store_true')), - (['--postfix'], - dict(help='Install Postfix stack', action='store_true')), - (['--wpcli'], - dict(help='Install WPCLI stack', action='store_true')), - (['--phpmyadmin'], - dict(help='Install PHPMyAdmin stack', action='store_true')), - (['--adminer'], - dict(help='Install Adminer stack', action='store_true')), - (['--utils'], - dict(help='Install Utils stack', action='store_true')), - (['--pagespeed'], - dict(help='Install Pagespeed', action='store_true')), - (['--redis'], - dict(help='Install Redis', action='store_true')), - (['--phpredisadmin'], - dict(help='Install phpRedisAdmin', action='store_true')), - ] - usage = "ee stack (command) [options]" - - @expose(hide=True) - def default(self): - """default action of ee stack command""" - if self.app.pargs.pagespeed: - Log.error(self, "Pagespeed support has been dropped since EasyEngine v3.6.0",False) - Log.error(self, "Please run command again without `--pagespeed`",False) - Log.error(self, "For more details, read - https://easyengine.io/blog/disabling-pagespeed/") - else: - self.app.args.print_help() - - @expose(hide=True) - def pre_pref(self, apt_packages): - """Pre settings to do before installation packages""" - if set(EEVariables.ee_postfix).issubset(set(apt_packages)): - Log.debug(self, "Pre-seeding Postfix") - try: - EEShellExec.cmd_exec(self, "echo \"postfix postfix" - "/main_mailer_type string \'Internet Site" - "\'\"" - " | debconf-set-selections") - EEShellExec.cmd_exec(self, "echo \"postfix postfix/mailname" - " string $(hostname -f)\" | " - "debconf-set-selections") - except CommandExecutionError as e: - Log.error(self, "Failed to intialize postfix package") - - if set(EEVariables.ee_mysql).issubset(set(apt_packages)): - Log.info(self, "Adding repository for MySQL, please wait...") - mysql_pref = ("Package: *\nPin: origin sfo1.mirrors.digitalocean.com" - "\nPin-Priority: 1000\n") - with open('/etc/apt/preferences.d/' - 'MariaDB.pref', 'w') as mysql_pref_file: - mysql_pref_file.write(mysql_pref) - EERepo.add(self, repo_url=EEVariables.ee_mysql_repo) - Log.debug(self, 'Adding key for {0}' - .format(EEVariables.ee_mysql_repo)) - EERepo.add_key(self, '0xcbcb082a1bb943db', - keyserver="keyserver.ubuntu.com") - EERepo.add_key(self, '0xF1656F24C74CD1D8', - keyserver="keyserver.ubuntu.com") - chars = ''.join(random.sample(string.ascii_letters, 8)) - Log.debug(self, "Pre-seeding MySQL") - Log.debug(self, "echo \"mariadb-server-10.1 " - "mysql-server/root_password " - "password \" | " - "debconf-set-selections") - try: - EEShellExec.cmd_exec(self, "echo \"mariadb-server-10.1 " - "mysql-server/root_password " - "password {chars}\" | " - "debconf-set-selections" - .format(chars=chars), - log=False) - except CommandExecutionError as e: - Log.error("Failed to initialize MySQL package") - - Log.debug(self, "echo \"mariadb-server-10.1 " - "mysql-server/root_password_again " - "password \" | " - "debconf-set-selections") - try: - EEShellExec.cmd_exec(self, "echo \"mariadb-server-10.1 " - "mysql-server/root_password_again " - "password {chars}\" | " - "debconf-set-selections" - .format(chars=chars), - log=False) - except CommandExecutionError as e: - Log.error("Failed to initialize MySQL package") - - mysql_config = """ - [client] - user = root - password = {chars} - """.format(chars=chars) - config = configparser.ConfigParser() - config.read_string(mysql_config) - Log.debug(self, 'Writting configuration into MySQL file') - conf_path = "/etc/mysql/conf.d/my.cnf" - os.makedirs(os.path.dirname(conf_path), exist_ok=True) - with open(conf_path, encoding='utf-8', - mode='w') as configfile: - config.write(configfile) - Log.debug(self, 'Setting my.cnf permission') - EEFileUtils.chmod(self, "/etc/mysql/conf.d/my.cnf", 0o600) - - if set(EEVariables.ee_nginx).issubset(set(apt_packages)): - Log.info(self, "Adding repository for NGINX, please wait...") - EERepo.add(self, repo_url=EEVariables.ee_nginx_repo) - Log.debug(self, 'Adding ppa of Nginx') - EERepo.add_key(self, EEVariables.ee_nginx_key) - - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if set(EEVariables.ee_php7_0).issubset(set(apt_packages)) \ - or set(EEVariables.ee_php5_6).issubset(set(apt_packages)): - Log.info(self, "Adding repository for PHP, please wait...") - Log.debug(self, 'Adding ppa for PHP') - EERepo.add(self, ppa=EEVariables.ee_php_repo) - else: - if set(EEVariables.ee_php).issubset(set(apt_packages)): - Log.info(self, "Adding repository for PHP, please wait...") - # Add repository for php - if EEVariables.ee_platform_distro == 'debian': - if EEVariables.ee_platform_codename != 'jessie': - Log.debug(self, 'Adding repo_url of php for debian') - EERepo.add(self, repo_url=EEVariables.ee_php_repo) - Log.debug(self, 'Adding Dotdeb/php GPG key') - EERepo.add_key(self, '89DF5277') - else: - Log.debug(self, 'Adding ppa for PHP') - EERepo.add(self, ppa=EEVariables.ee_php_repo) - - if EEVariables.ee_platform_codename == 'jessie': - if set(EEVariables.ee_php7_0).issubset(set(apt_packages)): - Log.debug(self, 'Adding repo_url of php 7.0 for debian') - EERepo.add(self, repo_url=EEVariables.ee_php_repo) - Log.debug(self, 'Adding Dotdeb/php GPG key') - EERepo.add_key(self, '89DF5277') - - if set(EEVariables.ee_hhvm).issubset(set(apt_packages)): - if (EEVariables.ee_platform_codename != 'xenial' or EEVariables.ee_platform_codename != 'bionic'): - Log.info(self, "Adding repository for HHVM, please wait...") - if EEVariables.ee_platform_codename == 'precise': - Log.debug(self, 'Adding PPA for Boost') - EERepo.add(self, ppa=EEVariables.ee_boost_repo) - Log.debug(self, 'Adding ppa repo for HHVM') - EERepo.add(self, repo_url=EEVariables.ee_hhvm_repo) - Log.debug(self, 'Adding HHVM GPG Key') - EERepo.add_key(self, '0x5a16e7281be7a449') - else: - Log.info(self, "Using default Ubuntu repository for HHVM") - - if set(EEVariables.ee_mail).issubset(set(apt_packages)): - Log.debug(self, 'Executing the command debconf-set-selections.') - try: - EEShellExec.cmd_exec(self, "echo \"dovecot-core dovecot-core/" - "create-ssl-cert boolean yes\" " - "| debconf-set-selections") - EEShellExec.cmd_exec(self, "echo \"dovecot-core dovecot-core" - "/ssl-cert-name string $(hostname -f)\"" - " | debconf-set-selections") - except CommandExecutionError as e: - Log.error("Failed to initialize dovecot packages") - - if set(EEVariables.ee_redis).issubset(set(apt_packages)): - Log.info(self, "Adding repository for Redis, please wait...") - if EEVariables.ee_platform_distro == 'debian': - Log.debug(self, 'Adding repo_url of redis for debian') - EERepo.add(self, repo_url=EEVariables.ee_redis_repo) - Log.debug(self, 'Adding Dotdeb GPG key') - EERepo.add_key(self, '89DF5277') - else: - Log.debug(self, 'Adding ppa for redis') - EERepo.add(self, ppa=EEVariables.ee_redis_repo) - - @expose(hide=True) - def post_pref(self, apt_packages, packages): - """Post activity after installation of packages""" - if len(apt_packages): - if set(EEVariables.ee_postfix).issubset(set(apt_packages)): - EEGit.add(self, ["/etc/postfix"], - msg="Adding Postfix into Git") - EEService.reload_service(self, 'postfix') - - if set(EEVariables.ee_nginx).issubset(set(apt_packages)): - if set(["nginx-plus"]).issubset(set(apt_packages)) or set(["nginx"]).issubset(set(apt_packages)): - # Fix for white screen death with NGINX PLUS - if not EEFileUtils.grep(self, '/etc/nginx/fastcgi_params', - 'SCRIPT_FILENAME'): - with open('/etc/nginx/fastcgi_params', encoding='utf-8', - mode='a') as ee_nginx: - ee_nginx.write('fastcgi_param \tSCRIPT_FILENAME ' - '\t$request_filename;\n') - - if not (os.path.isfile('/etc/nginx/common/wpfc.conf')): - # Change EasyEngine Version in nginx.conf file - EEFileUtils.searchreplace(self, "/etc/nginx/nginx.conf", - "# add_header", - "add_header") - - EEFileUtils.searchreplace(self, "/etc/nginx/nginx.conf", - "\"EasyEngine\"", - "\"EasyEngine {0}\"" - .format(EEVariables.ee_version)) - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/conf.d/blockips.conf') - ee_nginx = open('/etc/nginx/conf.d/blockips.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'blockips.mustache', out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/conf.d/fastcgi.conf') - ee_nginx = open('/etc/nginx/conf.d/fastcgi.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'fastcgi.mustache', out=ee_nginx) - ee_nginx.close() - - data = dict(php="9000", debug="9001", hhvm="8000",php7="9070",debug7="9170", - hhvmconf=False, php7conf= True if EEAptGet.is_installed(self,'php7.0-fpm') else False ) - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/conf.d/upstream.conf') - ee_nginx = open('/etc/nginx/conf.d/upstream.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'upstream.mustache', out=ee_nginx) - ee_nginx.close() - - # Setup Nginx common directory - if not os.path.exists('/etc/nginx/common'): - Log.debug(self, 'Creating directory' - '/etc/nginx/common') - os.makedirs('/etc/nginx/common') - - # http2 = ("http2" if set(["nginx-mainline"]).issubset(set(apt_packages)) else "spdy") - data = dict(webroot=EEVariables.ee_webroot) - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/acl.conf') - ee_nginx = open('/etc/nginx/common/acl.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'acl.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/locations.conf') - ee_nginx = open('/etc/nginx/common/locations.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'locations.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/php.conf') - ee_nginx = open('/etc/nginx/common/php.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/w3tc.conf') - ee_nginx = open('/etc/nginx/common/w3tc.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'w3tc.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpcommon.conf') - ee_nginx = open('/etc/nginx/common/wpcommon.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpcommon.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpfc.conf') - ee_nginx = open('/etc/nginx/common/wpfc.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpfc.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpsc.conf') - ee_nginx = open('/etc/nginx/common/wpsc.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpsc.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpsubdir.conf') - ee_nginx = open('/etc/nginx/common/wpsubdir.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpsubdir.mustache', - out=ee_nginx) - ee_nginx.close() - - #php7 conf - if (EEVariables.ee_platform_codename == 'jessie' or EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') and (not - os.path.isfile("/etc/nginx/common/php7.conf")): - #data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/locations-php7.conf') - ee_nginx = open('/etc/nginx/common/locations-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'locations-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/php7.conf') - ee_nginx = open('/etc/nginx/common/php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/w3tc-php7.conf') - ee_nginx = open('/etc/nginx/common/w3tc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'w3tc-php7.mustache', out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpcommon-php7.conf') - ee_nginx = open('/etc/nginx/common/wpcommon-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpcommon-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpfc-php7.conf') - ee_nginx = open('/etc/nginx/common/wpfc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpfc-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpsc-php7.conf') - ee_nginx = open('/etc/nginx/common/wpsc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpsc-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis-php7.conf') - ee_nginx = open('/etc/nginx/common/redis-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - # Nginx-Plus does not have nginx package structure like this - # So creating directories - if set(["nginx-plus"]).issubset(set(apt_packages)) or set(["nginx"]).issubset(set(apt_packages)): - Log.info(self, - "Installing EasyEngine Configurations for" "NGINX") - if not os.path.exists('/etc/nginx/sites-available'): - Log.debug(self, 'Creating directory' - '/etc/nginx/sites-available') - os.makedirs('/etc/nginx/sites-available') - - if not os.path.exists('/etc/nginx/sites-enabled'): - Log.debug(self, 'Creating directory' - '/etc/nginx/sites-available') - os.makedirs('/etc/nginx/sites-enabled') - - # 22222 port settings - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/sites-available/' - '22222') - ee_nginx = open('/etc/nginx/sites-available/22222', - encoding='utf-8', mode='w') - self.app.render((data), '22222.mustache', - out=ee_nginx) - ee_nginx.close() - - passwd = ''.join([random.choice - (string.ascii_letters + string.digits) - for n in range(6)]) - try: - EEShellExec.cmd_exec(self, "printf \"easyengine:" - "$(openssl passwd -crypt " - "{password} 2> /dev/null)\n\"" - "> /etc/nginx/htpasswd-ee " - "2>/dev/null" - .format(password=passwd)) - except CommandExecutionError as e: - Log.error(self, "Failed to save HTTP Auth") - - # Create Symbolic link for 22222 - EEFileUtils.create_symlink(self, ['/etc/nginx/' - 'sites-available/' - '22222', - '/etc/nginx/' - 'sites-enabled/' - '22222']) - # Create log and cert folder and softlinks - if not os.path.exists('{0}22222/logs' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating directory " - "{0}22222/logs " - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/logs' - .format(EEVariables.ee_webroot)) - - if not os.path.exists('{0}22222/cert' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating directory " - "{0}22222/cert" - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/cert' - .format(EEVariables.ee_webroot)) - - EEFileUtils.create_symlink(self, ['/var/log/nginx/' - '22222.access.log', - '{0}22222/' - 'logs/access.log' - .format(EEVariables.ee_webroot)] - ) - - EEFileUtils.create_symlink(self, ['/var/log/nginx/' - '22222.error.log', - '{0}22222/' - 'logs/error.log' - .format(EEVariables.ee_webroot)] - ) - - try: - EEShellExec.cmd_exec(self, "openssl genrsa -out " - "{0}22222/cert/22222.key 2048" - .format(EEVariables.ee_webroot)) - EEShellExec.cmd_exec(self, "openssl req -new -batch " - "-subj /commonName=127.0.0.1/ " - "-key {0}22222/cert/22222.key " - "-out {0}22222/cert/" - "22222.csr" - .format(EEVariables.ee_webroot)) - - EEFileUtils.mvfile(self, "{0}22222/cert/22222.key" - .format(EEVariables.ee_webroot), - "{0}22222/cert/" - "22222.key.org" - .format(EEVariables.ee_webroot)) - - EEShellExec.cmd_exec(self, "openssl rsa -in " - "{0}22222/cert/" - "22222.key.org -out " - "{0}22222/cert/22222.key" - .format(EEVariables.ee_webroot)) - - EEShellExec.cmd_exec(self, "openssl x509 -req -days " - "3652 -in {0}22222/cert/" - "22222.csr -signkey {0}" - "22222/cert/22222.key -out " - "{0}22222/cert/22222.crt" - .format(EEVariables.ee_webroot)) - - except CommandExecutionError as e: - Log.error(self, "Failed to generate SSL for 22222") - - # Nginx Configation into GIT - EEGit.add(self, - ["/etc/nginx"], msg="Adding Nginx into Git") - EEService.reload_service(self, 'nginx') - if set(["nginx-plus"]).issubset(set(apt_packages)) or set(["nginx"]).issubset(set(apt_packages)): - EEShellExec.cmd_exec(self, "sed -i -e 's/^user/#user/'" - " -e '/^#user/a user" - "\ www-data\;'" - " /etc/nginx/nginx.conf") - if not EEShellExec.cmd_exec(self, "cat /etc/nginx/" - "nginx.conf | grep -q " - "'/etc/nginx/sites-enabled'"): - EEShellExec.cmd_exec(self, "sed -i '/\/etc\/" - "nginx\/conf\.d\/\*" - "\.conf/a \ include" - "\ \/etc\/nginx\/sites-enabled" - "\/*;' /etc/nginx/nginx.conf") - - # EasyEngine config for NGINX plus - data['version'] = EEVariables.ee_version - Log.debug(self, 'Writting for nginx plus configuration' - ' to file /etc/nginx/conf.d/ee-plus.conf') - ee_nginx = open('/etc/nginx/conf.d/ee-plus.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'ee-plus.mustache', - out=ee_nginx) - ee_nginx.close() - - print("HTTP Auth User Name: easyengine" - + "\nHTTP Auth Password : {0}".format(passwd)) - EEService.reload_service(self, 'nginx') - else: - self.msg = (self.msg + ["HTTP Auth User Name: easyengine"] - + ["HTTP Auth Password : {0}".format(passwd)]) - else: - EEService.restart_service(self, 'nginx') - - if EEAptGet.is_installed(self,'redis-server'): - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis.conf")): - - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis.conf') - ee_nginx = open('/etc/nginx/common/redis.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis-hhvm.conf")): - - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis-hhvm.conf') - ee_nginx = open('/etc/nginx/common/redis-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis-php7.conf")): - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis-php7.conf') - ee_nginx = open('/etc/nginx/common/redis-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/" - "upstream.conf", - "redis"): - with open("/etc/nginx/conf.d/upstream.conf", - "a") as redis_file: - redis_file.write("upstream redis {\n" - " server 127.0.0.1:6379;\n" - " keepalive 10;\n}\n") - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/conf.d/redis.conf")): - with open("/etc/nginx/conf.d/redis.conf", "a") as redis_file: - redis_file.write("# Log format Settings\n" - "log_format rt_cache_redis '$remote_addr $upstream_response_time $srcache_fetch_status [$time_local] '\n" - "'$http_host \"$request\" $status $body_bytes_sent '\n" - "'\"$http_referer\" \"$http_user_agent\"';\n") - #setup nginx common folder for php7 - if self.app.pargs.php7: - if os.path.isdir("/etc/nginx/common") and (not - os.path.isfile("/etc/nginx/common/php7.conf")): - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/locations-php7.conf') - ee_nginx = open('/etc/nginx/common/locations-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'locations-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/php7.conf') - ee_nginx = open('/etc/nginx/common/php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/w3tc-php7.conf') - ee_nginx = open('/etc/nginx/common/w3tc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'w3tc-php7.mustache', out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpcommon-php7.conf') - ee_nginx = open('/etc/nginx/common/wpcommon-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpcommon-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpfc-php7.conf') - ee_nginx = open('/etc/nginx/common/wpfc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpfc-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpsc-php7.conf') - ee_nginx = open('/etc/nginx/common/wpsc-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpsc-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isdir("/etc/nginx/common") and (not os.path.isfile("/etc/nginx/common/redis-php7.conf")): - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis-php7.conf') - ee_nginx = open('/etc/nginx/common/redis-php7.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis-php7.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/upstream.conf", - "php7"): - with open("/etc/nginx/conf.d/upstream.conf", "a") as php_file: - php_file.write("upstream php7 {\nserver 127.0.0.1:9070;\n}\n" - "upstream debug7 {\nserver 127.0.0.1:9170;\n}\n") - - # Set up pagespeed config - if self.app.pargs.pagespeed: - if (os.path.isfile('/etc/nginx/nginx.conf') and - (not os.path.isfile('/etc/nginx/conf.d/pagespeed.conf'))): - # Pagespeed configuration - data = dict() - Log.debug(self, 'Writting the Pagespeed Global ' - 'configuration to file /etc/nginx/conf.d/' - 'pagespeed.conf') - ee_nginx = open('/etc/nginx/conf.d/pagespeed.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'pagespeed-global.mustache', - out=ee_nginx) - ee_nginx.close() - - if set(EEVariables.ee_hhvm).issubset(set(apt_packages)): - - EEShellExec.cmd_exec(self, "update-rc.d hhvm defaults") - - EEFileUtils.searchreplace(self, "/etc/hhvm/server.ini", - "9000", "8000") - if (EEVariables.ee_platform_codename != 'xenial' or EEVariables.ee_platform_codename != 'bionic'): - EEFileUtils.searchreplace(self, "/etc/nginx/hhvm.conf", - "9000", "8000") - - with open("/etc/hhvm/php.ini", "a") as hhvm_file: - hhvm_file.write("hhvm.log.header = true\n" - "hhvm.log.natives_stack_trace = true\n" - "hhvm.mysql.socket = " - "/var/run/mysqld/mysqld.sock\n" - "hhvm.pdo_mysql.socket = " - "/var/run/mysqld/mysqld.sock\n" - "hhvm.mysqli.socket = " - "/var/run/mysqld/mysqld.sock\n") - - with open("/etc/hhvm/server.ini", "a") as hhvm_file: - hhvm_file.write("hhvm.server.ip = 127.0.0.1\n") - - if os.path.isfile("/etc/nginx/conf.d/fastcgi.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/" - "fastcgi.conf", - "fastcgi_keep_conn"): - with open("/etc/nginx/conf.d/fastcgi.conf", - "a") as hhvm_file: - hhvm_file.write("fastcgi_keep_conn on;\n") - - if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/" - "upstream.conf", - "hhvm"): - with open("/etc/nginx/conf.d/upstream.conf", - "a") as hhvm_file: - hhvm_file.write("upstream hhvm {\nserver " - "127.0.0.1:8000;\n" - "server 127.0.0.1:9000 backup;\n}" - "\n") - - EEGit.add(self, ["/etc/hhvm"], msg="Adding HHVM into Git") - EEService.restart_service(self, 'hhvm') - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/php-hhvm.conf")): - - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/php-hhvm.conf') - ee_nginx = open('/etc/nginx/common/php-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/w3tc-hhvm.conf') - ee_nginx = open('/etc/nginx/common/w3tc-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'w3tc-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpfc-hhvm.conf') - ee_nginx = open('/etc/nginx/common/wpfc-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpfc-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/wpsc-hhvm.conf') - ee_nginx = open('/etc/nginx/common/wpsc-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'wpsc-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - if not EEService.reload_service(self, 'nginx'): - Log.error(self, "Failed to reload Nginx, please check " - "output of `nginx -t`") - - if set(EEVariables.ee_redis).issubset(set(apt_packages)): - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis.conf")): - - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis.conf') - ee_nginx = open('/etc/nginx/common/redis.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/common/redis-hhvm.conf")): - - data = dict() - Log.debug(self, 'Writting the nginx configuration to ' - 'file /etc/nginx/common/redis-hhvm.conf') - ee_nginx = open('/etc/nginx/common/redis-hhvm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'redis-hhvm.mustache', - out=ee_nginx) - ee_nginx.close() - - if os.path.isfile("/etc/nginx/conf.d/upstream.conf"): - if not EEFileUtils.grep(self, "/etc/nginx/conf.d/" - "upstream.conf", - "redis"): - with open("/etc/nginx/conf.d/upstream.conf", - "a") as redis_file: - redis_file.write("upstream redis {\n" - " server 127.0.0.1:6379;\n" - " keepalive 10;\n}\n") - - if os.path.isfile("/etc/nginx/nginx.conf") and (not - os.path.isfile("/etc/nginx/conf.d/redis.conf")): - with open("/etc/nginx/conf.d/redis.conf", "a") as redis_file: - redis_file.write("# Log format Settings\n" - "log_format rt_cache_redis '$remote_addr $upstream_response_time $srcache_fetch_status [$time_local] '\n" - "'$http_host \"$request\" $status $body_bytes_sent '\n" - "'\"$http_referer\" \"$http_user_agent\"';\n") - - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise') and set(EEVariables.ee_php).issubset(set(apt_packages)): - # Create log directories - if not os.path.exists('/var/log/php5/'): - Log.debug(self, 'Creating directory /var/log/php5/') - os.makedirs('/var/log/php5/') - - # For debian install xdebug - - if (EEVariables.ee_platform_distro == "debian" and - EEVariables.ee_platform_codename == 'wheezy'): - EEShellExec.cmd_exec(self, "pecl install xdebug") - - with open("/etc/php5/mods-available/xdebug.ini", - encoding='utf-8', mode='a') as myfile: - myfile.write("zend_extension=/usr/lib/php5/20131226/" - "xdebug.so\n") - - EEFileUtils.create_symlink(self, ["/etc/php5/" - "mods-available/xdebug.ini", - "/etc/php5/fpm/conf.d" - "/20-xedbug.ini"]) - - # Parse etc/php5/fpm/php.ini - config = configparser.ConfigParser() - Log.debug(self, "configuring php file /etc/php5/fpm/php.ini") - config.read('/etc/php5/fpm/php.ini') - config['PHP']['expose_php'] = 'Off' - config['PHP']['post_max_size'] = '100M' - config['PHP']['upload_max_filesize'] = '100M' - config['PHP']['max_execution_time'] = '300' - config['PHP']['date.timezone'] = EEVariables.ee_timezone - with open('/etc/php5/fpm/php.ini', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "Writting php configuration into " - "/etc/php5/fpm/php.ini") - config.write(configfile) - - #configure /etc/php5/fpm/php-fpm.conf - data = dict(pid="/run/php5-fpm.pid", error_log="/var/log/php5/fpm.log", - include="/etc/php5/fpm/pool.d/*.conf") - Log.debug(self, "writting php configuration into " - "/etc/php5/fpm/php-fpm.conf") - ee_php_fpm = open('/etc/php5/fpm/php-fpm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-fpm.mustache', out=ee_php_fpm) - ee_php_fpm.close() - - - # Parse /etc/php5/fpm/pool.d/www.conf - config = configparser.ConfigParser() - config.read_file(codecs.open('/etc/php5/fpm/pool.d/www.conf', - "r", "utf8")) - config['www']['ping.path'] = '/ping' - config['www']['pm.status_path'] = '/status' - config['www']['pm.max_requests'] = '500' - config['www']['pm.max_children'] = '100' - config['www']['pm.start_servers'] = '20' - config['www']['pm.min_spare_servers'] = '10' - config['www']['pm.max_spare_servers'] = '30' - config['www']['request_terminate_timeout'] = '300' - config['www']['pm'] = 'ondemand' - config['www']['listen'] = '127.0.0.1:9000' - with codecs.open('/etc/php5/fpm/pool.d/www.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php5/fpm/pool.d/www.conf") - config.write(configfile) - - # Generate /etc/php5/fpm/pool.d/debug.conf - EEFileUtils.copyfile(self, "/etc/php5/fpm/pool.d/www.conf", - "/etc/php5/fpm/pool.d/debug.conf") - EEFileUtils.searchreplace(self, "/etc/php5/fpm/pool.d/" - "debug.conf", "[www]", "[debug]") - config = configparser.ConfigParser() - config.read('/etc/php5/fpm/pool.d/debug.conf') - config['debug']['listen'] = '127.0.0.1:9001' - config['debug']['rlimit_core'] = 'unlimited' - config['debug']['slowlog'] = '/var/log/php5/slow.log' - config['debug']['request_slowlog_timeout'] = '10s' - with open('/etc/php5/fpm/pool.d/debug.conf', - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php5/fpm/pool.d/debug.conf") - config.write(confifile) - - with open("/etc/php5/fpm/pool.d/debug.conf", - encoding='utf-8', mode='a') as myfile: - myfile.write("php_admin_value[xdebug.profiler_output_dir] " - "= /tmp/ \nphp_admin_value[xdebug.profiler_" - "output_name] = cachegrind.out.%p-%H-%R " - "\nphp_admin_flag[xdebug.profiler_enable" - "_trigger] = on \nphp_admin_flag[xdebug." - "profiler_enable] = off\n") - - # Disable xdebug - EEFileUtils.searchreplace(self, "/etc/php5/mods-available/" - "xdebug.ini", - "zend_extension", - ";zend_extension") - - # PHP and Debug pull configuration - if not os.path.exists('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/fpm/status/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)) - open('{0}22222/htdocs/fpm/status/debug' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - open('{0}22222/htdocs/fpm/status/php' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - - # Write info.php - if not os.path.exists('{0}22222/htdocs/php/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/php/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/php' - .format(EEVariables.ee_webroot)) - - with open("{0}22222/htdocs/php/info.php" - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='w') as myfile: - myfile.write("") - - EEFileUtils.chown(self, "{0}22222" - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, recursive=True) - - EEGit.add(self, ["/etc/php5"], msg="Adding PHP into Git") - EEService.restart_service(self, 'php5-fpm') - - - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') and set(EEVariables.ee_php5_6).issubset(set(apt_packages)): - # Create log directories - if not os.path.exists('/var/log/php/5.6/'): - Log.debug(self, 'Creating directory /var/log/php/5.6/') - os.makedirs('/var/log/php/5.6/') - - # Parse etc/php/5.6/fpm/php.ini - config = configparser.ConfigParser() - Log.debug(self, "configuring php file /etc/php/5.6/fpm/php.ini") - config.read('/etc/php/5.6/fpm/php.ini') - config['PHP']['expose_php'] = 'Off' - config['PHP']['post_max_size'] = '100M' - config['PHP']['upload_max_filesize'] = '100M' - config['PHP']['max_execution_time'] = '300' - config['PHP']['date.timezone'] = EEVariables.ee_timezone - with open('/etc/php/5.6/fpm/php.ini', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "Writting php configuration into " - "/etc/php/5.6/fpm/php.ini") - config.write(configfile) - - # Parse /etc/php/5.6/fpm/php-fpm.conf - data = dict(pid="/run/php/php5.6-fpm.pid", error_log="/var/log/php/5.6/fpm.log", - include="/etc/php/5.6/fpm/pool.d/*.conf") - Log.debug(self, "writting php5 configuration into " - "/etc/php/5.6/fpm/php-fpm.conf") - ee_php_fpm = open('/etc/php/5.6/fpm/php-fpm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-fpm.mustache', out=ee_php_fpm) - ee_php_fpm.close() - - # Parse /etc/php/5.6/fpm/pool.d/www.conf - config = configparser.ConfigParser() - config.read_file(codecs.open('/etc/php/5.6/fpm/pool.d/www.conf', - "r", "utf8")) - config['www']['ping.path'] = '/ping' - config['www']['pm.status_path'] = '/status' - config['www']['pm.max_requests'] = '500' - config['www']['pm.max_children'] = '100' - config['www']['pm.start_servers'] = '20' - config['www']['pm.min_spare_servers'] = '10' - config['www']['pm.max_spare_servers'] = '30' - config['www']['request_terminate_timeout'] = '300' - config['www']['pm'] = 'ondemand' - config['www']['listen'] = '127.0.0.1:9000' - with codecs.open('/etc/php/5.6/fpm/pool.d/www.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php/5.6/fpm/pool.d/www.conf") - config.write(configfile) - - # Generate /etc/php/5.6/fpm/pool.d/debug.conf - EEFileUtils.copyfile(self, "/etc/php/5.6/fpm/pool.d/www.conf", - "/etc/php/5.6/fpm/pool.d/debug.conf") - EEFileUtils.searchreplace(self, "/etc/php/5.6/fpm/pool.d/" - "debug.conf", "[www]", "[debug]") - config = configparser.ConfigParser() - config.read('/etc/php/5.6/fpm/pool.d/debug.conf') - config['debug']['listen'] = '127.0.0.1:9001' - config['debug']['rlimit_core'] = 'unlimited' - config['debug']['slowlog'] = '/var/log/php/5.6/slow.log' - config['debug']['request_slowlog_timeout'] = '10s' - with open('/etc/php/5.6/fpm/pool.d/debug.conf', - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php/5.6/fpm/pool.d/debug.conf") - config.write(confifile) - - with open("/etc/php/5.6/fpm/pool.d/debug.conf", - encoding='utf-8', mode='a') as myfile: - myfile.write("php_admin_value[xdebug.profiler_output_dir] " - "= /tmp/ \nphp_admin_value[xdebug.profiler_" - "output_name] = cachegrind.out.%p-%H-%R " - "\nphp_admin_flag[xdebug.profiler_enable" - "_trigger] = on \nphp_admin_flag[xdebug." - "profiler_enable] = off\n") - - # Disable xdebug - if not EEShellExec.cmd_exec(self, "grep -q \';zend_extension\' /etc/php/5.6/mods-available/xdebug.ini"): - EEFileUtils.searchreplace(self, "/etc/php/5.6/mods-available/" - "xdebug.ini", - "zend_extension", - ";zend_extension") - - # PHP and Debug pull configuration - if not os.path.exists('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/fpm/status/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)) - open('{0}22222/htdocs/fpm/status/debug' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - open('{0}22222/htdocs/fpm/status/php' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - - # Write info.php - if not os.path.exists('{0}22222/htdocs/php/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/php/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/php' - .format(EEVariables.ee_webroot)) - - with open("{0}22222/htdocs/php/info.php" - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='w') as myfile: - myfile.write("") - - EEFileUtils.chown(self, "{0}22222" - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, recursive=True) - - EEGit.add(self, ["/etc/php"], msg="Adding PHP into Git") - EEService.restart_service(self, 'php5.6-fpm') - - #PHP7.0 configuration for debian - if (EEVariables.ee_platform_codename == 'jessie' ) and set(EEVariables.ee_php7_0).issubset(set(apt_packages)): - # Create log directories - if not os.path.exists('/var/log/php/7.0/'): - Log.debug(self, 'Creating directory /var/log/php/7.0/') - os.makedirs('/var/log/php/7.0/') - - # Parse etc/php/7.0/fpm/php.ini - config = configparser.ConfigParser() - Log.debug(self, "configuring php file /etc/php/7.0/fpm/php.ini") - config.read('/etc/php/7.0/fpm/php.ini') - config['PHP']['expose_php'] = 'Off' - config['PHP']['post_max_size'] = '100M' - config['PHP']['upload_max_filesize'] = '100M' - config['PHP']['max_execution_time'] = '300' - config['PHP']['date.timezone'] = EEVariables.ee_timezone - with open('/etc/php/7.0/fpm/php.ini', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "Writting php configuration into " - "/etc/php/7.0/fpm/php.ini") - config.write(configfile) - - # Parse /etc/php/7.0/fpm/php-fpm.conf - data = dict(pid="/run/php/php7.0-fpm.pid", error_log="/var/log/php7.0-fpm.log", - include="/etc/php/7.0/fpm/pool.d/*.conf") - Log.debug(self, "writting php 7.0 configuration into " - "/etc/php/7.0/fpm/php-fpm.conf") - ee_php_fpm = open('/etc/php/7.0/fpm/php-fpm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-fpm.mustache', out=ee_php_fpm) - ee_php_fpm.close() - - # Parse /etc/php/7.0/fpm/pool.d/www.conf - config = configparser.ConfigParser() - config.read_file(codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', - "r", "utf8")) - config['www']['ping.path'] = '/ping' - config['www']['pm.status_path'] = '/status' - config['www']['pm.max_requests'] = '500' - config['www']['pm.max_children'] = '100' - config['www']['pm.start_servers'] = '20' - config['www']['pm.min_spare_servers'] = '10' - config['www']['pm.max_spare_servers'] = '30' - config['www']['request_terminate_timeout'] = '300' - config['www']['pm'] = 'ondemand' - config['www']['listen'] = '127.0.0.1:9070' - with codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php/7.0/fpm/pool.d/www.conf") - config.write(configfile) - - # Generate /etc/php/7.0/fpm/pool.d/debug.conf - EEFileUtils.copyfile(self, "/etc/php/7.0/fpm/pool.d/www.conf", - "/etc/php/7.0/fpm/pool.d/debug.conf") - EEFileUtils.searchreplace(self, "/etc/php/7.0/fpm/pool.d/" - "debug.conf", "[www]", "[debug]") - config = configparser.ConfigParser() - config.read('/etc/php/7.0/fpm/pool.d/debug.conf') - config['debug']['listen'] = '127.0.0.1:9170' - config['debug']['rlimit_core'] = 'unlimited' - config['debug']['slowlog'] = '/var/log/php/7.0/slow.log' - config['debug']['request_slowlog_timeout'] = '10s' - with open('/etc/php/7.0/fpm/pool.d/debug.conf', - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php/7.0/fpm/pool.d/debug.conf") - config.write(confifile) - - with open("/etc/php/7.0/fpm/pool.d/debug.conf", - encoding='utf-8', mode='a') as myfile: - myfile.write("php_admin_value[xdebug.profiler_output_dir] " - "= /tmp/ \nphp_admin_value[xdebug.profiler_" - "output_name] = cachegrind.out.%p-%H-%R " - "\nphp_admin_flag[xdebug.profiler_enable" - "_trigger] = on \nphp_admin_flag[xdebug." - "profiler_enable] = off\n") - - # Disable xdebug - if not EEShellExec.cmd_exec(self, "grep -q \';zend_extension\' /etc/php/7.0/mods-available/xdebug.ini"): - EEFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" - "xdebug.ini", - "zend_extension", - ";zend_extension") - - # PHP and Debug pull configuration - if not os.path.exists('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/fpm/status/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)) - open('{0}22222/htdocs/fpm/status/debug' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - open('{0}22222/htdocs/fpm/status/php' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - - # Write info.php - if not os.path.exists('{0}22222/htdocs/php/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/php/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/php' - .format(EEVariables.ee_webroot)) - - with open("{0}22222/htdocs/php/info.php" - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='w') as myfile: - myfile.write("") - - EEFileUtils.chown(self, "{0}22222" - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, recursive=True) - - EEGit.add(self, ["/etc/php"], msg="Adding PHP into Git") - EEService.restart_service(self, 'php7.0-fpm') - - #preconfiguration for php7.0 - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic') and set(EEVariables.ee_php7_0).issubset(set(apt_packages)): - # Create log directories - if not os.path.exists('/var/log/php/7.0/'): - Log.debug(self, 'Creating directory /var/log/php/7.0/') - os.makedirs('/var/log/php/7.0/') - - # Parse etc/php/7.0/fpm/php.ini - config = configparser.ConfigParser() - Log.debug(self, "configuring php file /etc/php/7.0/fpm/php.ini") - config.read('/etc/php/7.0/fpm/php.ini') - config['PHP']['expose_php'] = 'Off' - config['PHP']['post_max_size'] = '100M' - config['PHP']['upload_max_filesize'] = '100M' - config['PHP']['max_execution_time'] = '300' - config['PHP']['date.timezone'] = EEVariables.ee_timezone - with open('/etc/php/7.0/fpm/php.ini', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "Writting php configuration into " - "/etc/php/7.0/fpm/php.ini") - config.write(configfile) - - # Parse /etc/php/7.0/fpm/php-fpm.conf - data = dict(pid="/run/php/php7.0-fpm.pid", error_log="/var/log/php/7.0/fpm.log", - include="/etc/php/7.0/fpm/pool.d/*.conf") - Log.debug(self, "writting php 7.0 configuration into " - "/etc/php/7.0/fpm/php-fpm.conf") - ee_php_fpm = open('/etc/php/7.0/fpm/php-fpm.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'php-fpm.mustache', out=ee_php_fpm) - ee_php_fpm.close() - - # Parse /etc/php/7.0/fpm/pool.d/www.conf - config = configparser.ConfigParser() - config.read_file(codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', - "r", "utf8")) - config['www']['ping.path'] = '/ping' - config['www']['pm.status_path'] = '/status' - config['www']['pm.max_requests'] = '500' - config['www']['pm.max_children'] = '100' - config['www']['pm.start_servers'] = '20' - config['www']['pm.min_spare_servers'] = '10' - config['www']['pm.max_spare_servers'] = '30' - config['www']['request_terminate_timeout'] = '300' - config['www']['pm'] = 'ondemand' - config['www']['listen'] = '127.0.0.1:9070' - with codecs.open('/etc/php/7.0/fpm/pool.d/www.conf', - encoding='utf-8', mode='w') as configfile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php/7.0/fpm/pool.d/www.conf") - config.write(configfile) - - # Generate /etc/php/7.0/fpm/pool.d/debug.conf - EEFileUtils.copyfile(self, "/etc/php/7.0/fpm/pool.d/www.conf", - "/etc/php/7.0/fpm/pool.d/debug.conf") - EEFileUtils.searchreplace(self, "/etc/php/7.0/fpm/pool.d/" - "debug.conf", "[www]", "[debug]") - config = configparser.ConfigParser() - config.read('/etc/php/7.0/fpm/pool.d/debug.conf') - config['debug']['listen'] = '127.0.0.1:9170' - config['debug']['rlimit_core'] = 'unlimited' - config['debug']['slowlog'] = '/var/log/php/7.0/slow.log' - config['debug']['request_slowlog_timeout'] = '10s' - with open('/etc/php/7.0/fpm/pool.d/debug.conf', - encoding='utf-8', mode='w') as confifile: - Log.debug(self, "writting PHP5 configuration into " - "/etc/php/7.0/fpm/pool.d/debug.conf") - config.write(confifile) - - with open("/etc/php/7.0/fpm/pool.d/debug.conf", - encoding='utf-8', mode='a') as myfile: - myfile.write("php_admin_value[xdebug.profiler_output_dir] " - "= /tmp/ \nphp_admin_value[xdebug.profiler_" - "output_name] = cachegrind.out.%p-%H-%R " - "\nphp_admin_flag[xdebug.profiler_enable" - "_trigger] = on \nphp_admin_flag[xdebug." - "profiler_enable] = off\n") - - # Disable xdebug - if not EEShellExec.cmd_exec(self, "grep -q \';zend_extension\' /etc/php/7.0/mods-available/xdebug.ini"): - EEFileUtils.searchreplace(self, "/etc/php/7.0/mods-available/" - "xdebug.ini", - "zend_extension", - ";zend_extension") - - # PHP and Debug pull configuration - if not os.path.exists('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/fpm/status/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/fpm/status/' - .format(EEVariables.ee_webroot)) - open('{0}22222/htdocs/fpm/status/debug' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - open('{0}22222/htdocs/fpm/status/php' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='a').close() - - # Write info.php - if not os.path.exists('{0}22222/htdocs/php/' - .format(EEVariables.ee_webroot)): - Log.debug(self, 'Creating directory ' - '{0}22222/htdocs/php/ ' - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/php' - .format(EEVariables.ee_webroot)) - - with open("{0}22222/htdocs/php/info.php" - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='w') as myfile: - myfile.write("") - - EEFileUtils.chown(self, "{0}22222" - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, recursive=True) - - EEGit.add(self, ["/etc/php"], msg="Adding PHP into Git") - EEService.restart_service(self, 'php7.0-fpm') - - - - if set(EEVariables.ee_mysql).issubset(set(apt_packages)): - # TODO: Currently we are using, we need to remove it in future - # config = configparser.ConfigParser() - # config.read('/etc/mysql/my.cnf') - # config['mysqld']['wait_timeout'] = 30 - # config['mysqld']['interactive_timeout'] = 60 - # config['mysqld']['performance_schema'] = 0 - # with open('/etc/mysql/my.cnf', 'w') as configfile: - # config.write(configfile) - if not os.path.isfile("/etc/mysql/my.cnf"): - config = ("[mysqld]\nwait_timeout = 30\n" - "interactive_timeout=60\nperformance_schema = 0" - "\nquery_cache_type = 1") - config_file = open("/etc/mysql/my.cnf", - encoding='utf-8', mode='w') - config_file.write(config) - config_file.close() - else: - try: - EEShellExec.cmd_exec(self, "sed -i \"/#max_conn" - "ections/a wait_timeout = 30 \\n" - "interactive_timeout = 60 \\n" - "performance_schema = 0\\n" - "query_cache_type = 1 \" " - "/etc/mysql/my.cnf") - except CommandExecutionError as e: - Log.error(self, "Unable to update MySQL file") - - # Set MySQLTuner permission - EEFileUtils.chmod(self, "/usr/bin/mysqltuner", 0o775) - - EEGit.add(self, ["/etc/mysql"], msg="Adding MySQL into Git") - EEService.reload_service(self, 'mysql') - - if set(EEVariables.ee_mail).issubset(set(apt_packages)): - Log.debug(self, "Adding user") - try: - EEShellExec.cmd_exec(self, "adduser --uid 5000 --home /var" - "/vmail --disabled-password --gecos " - "'' vmail") - except CommandExecutionError as e: - Log.error(self, "Unable to add vmail user for mail server") - try: - EEShellExec.cmd_exec(self, "openssl req -new -x509 -days" - " 3650 " - "-nodes -subj /commonName={hostname}" - "/emailAddress={email} -out /etc/ssl" - "/certs/dovecot." - "pem -keyout " - "/etc/ssl/private/dovecot.pem" - .format(hostname=EEVariables.ee_fqdn, - email=EEVariables.ee_email)) - except CommandExecutionError as e: - Log.error(self, "Unable to generate PEM key for dovecot") - Log.debug(self, "Setting Privileges to " - "/etc/ssl/private/dovecot.pem file ") - EEFileUtils.chmod(self, "/etc/ssl/private/dovecot.pem", 0o600) - - # Custom Dovecot configuration by EasyEngine - data = dict() - Log.debug(self, "Writting configuration into file" - "/etc/dovecot/conf.d/auth-sql.conf.ext ") - ee_dovecot = open('/etc/dovecot/conf.d/auth-sql.conf.ext', - encoding='utf-8', mode='w') - self.app.render((data), 'auth-sql-conf.mustache', - out=ee_dovecot) - ee_dovecot.close() - - data = dict(email=EEVariables.ee_email) - Log.debug(self, "Writting configuration into file" - "/etc/dovecot/conf.d/99-ee.conf ") - ee_dovecot = open('/etc/dovecot/conf.d/99-ee.conf', - encoding='utf-8', mode='w') - self.app.render((data), 'dovecot.mustache', out=ee_dovecot) - ee_dovecot.close() - try: - EEShellExec.cmd_exec(self, "sed -i \"s/\\!include " - "auth-system.conf.ext/#\\!include " - "auth-system.conf.ext/\" " - "/etc/dovecot/conf.d/10-auth.conf") - - EEShellExec.cmd_exec(self, "sed -i \"s\'/etc/dovecot/" - "dovecot.pem\'/etc/ssl/certs/" - "dovecot.pem" - "\'\" /etc/dovecot/conf.d/" - "10-ssl.conf") - EEShellExec.cmd_exec(self, "sed -i \"s\'/etc/dovecot/" - "private/dovecot.pem\'/etc/ssl/" - "private" - "/dovecot.pem\'\" /etc/dovecot/" - "conf.d/" - "10-ssl.conf") - - # Custom Postfix configuration needed with Dovecot - # Changes in master.cf - # TODO: Find alternative for sed in Python - EEShellExec.cmd_exec(self, "sed -i \'s/#submission/" - "submission/\'" - " /etc/postfix/master.cf") - EEShellExec.cmd_exec(self, "sed -i \'s/#smtps/smtps/\'" - " /etc/postfix/master.cf") - - EEShellExec.cmd_exec(self, "postconf -e \"smtpd_sasl_type " - "= dovecot\"") - EEShellExec.cmd_exec(self, "postconf -e \"smtpd_sasl_path " - "= private/auth\"") - EEShellExec.cmd_exec(self, "postconf -e \"" - "smtpd_sasl_auth_enable = " - "yes\"") - EEShellExec.cmd_exec(self, "postconf -e \"" - " smtpd_relay_restrictions =" - " permit_sasl_authenticated, " - " permit_mynetworks, " - " reject_unauth_destination\"") - - EEShellExec.cmd_exec(self, "postconf -e \"" - "smtpd_tls_mandatory_" - "protocols = !SSLv2,!SSLv3\"") - EEShellExec.cmd_exec(self, "postconf -e \"smtp_tls_" - "mandatory_protocols = !SSLv2," - "!SSLv3\"") - EEShellExec.cmd_exec(self, "postconf -e \"smtpd_tls" - "_protocols = !SSLv2,!SSLv3\"") - EEShellExec.cmd_exec(self, "postconf -e \"smtp_tls" - "_protocols = !SSLv2,!SSLv3\"") - EEShellExec.cmd_exec(self, "postconf -e \"mydestination " - "= localhost\"") - EEShellExec.cmd_exec(self, "postconf -e \"virtual" - "_transport " - "= lmtp:unix:private/dovecot-lmtp\"") - EEShellExec.cmd_exec(self, "postconf -e \"virtual_uid_" - "maps = static:5000\"") - EEShellExec.cmd_exec(self, "postconf -e \"virtual_gid_" - "maps = static:5000\"") - EEShellExec.cmd_exec(self, "postconf -e \"" - " virtual_mailbox_domains = " - "mysql:/etc/postfix/mysql/virtual_" - "domains_maps.cf\"") - EEShellExec.cmd_exec(self, "postconf -e \"virtual_mailbox" - "_maps" - " = mysql:/etc/postfix/mysql/virtual_" - "mailbox_maps.cf\"") - EEShellExec.cmd_exec(self, "postconf -e \"virtual_alias" - "_maps " - "= mysql:/etc/postfix/mysql/virtual_" - "alias_maps.cf\"") - EEShellExec.cmd_exec(self, "openssl req -new -x509 -days " - " 3650 -nodes -subj /commonName=" - "{hostname}/emailAddress={email}" - " -out /etc/ssl/certs/postfix.pem" - " -keyout /etc/ssl/private/" - "postfix.pem" - .format(hostname=EEVariables.ee_fqdn, - email=EEVariables.ee_email)) - EEShellExec.cmd_exec(self, "chmod 0600 /etc/ssl/private" - "/postfix.pem") - EEShellExec.cmd_exec(self, "postconf -e \"smtpd_tls_cert_" - "file = /etc/ssl/certs/postfix.pem\"") - EEShellExec.cmd_exec(self, "postconf -e \"smtpd_tls_key_" - "file = /etc/ssl/private/" - "postfix.pem\"") - - except CommandExecutionError as e: - Log.Error(self, "Failed to update Dovecot configuration") - - # Sieve configuration - if not os.path.exists('/var/lib/dovecot/sieve/'): - Log.debug(self, 'Creating directory ' - '/var/lib/dovecot/sieve/ ') - os.makedirs('/var/lib/dovecot/sieve/') - - # Custom sieve configuration by EasyEngine - data = dict() - Log.debug(self, "Writting configuration of EasyEngine into " - "file /var/lib/dovecot/sieve/default.sieve") - ee_sieve = open('/var/lib/dovecot/sieve/default.sieve', - encoding='utf-8', mode='w') - self.app.render((data), 'default-sieve.mustache', - out=ee_sieve) - ee_sieve.close() - - # Compile sieve rules - Log.debug(self, "Setting Privileges to dovecot ") - # EEShellExec.cmd_exec(self, "chown -R vmail:vmail /var/lib" - # "/dovecot") - EEFileUtils.chown(self, "/var/lib/dovecot", 'vmail', 'vmail', - recursive=True) - try: - EEShellExec.cmd_exec(self, "sievec /var/lib/dovecot/" - "/sieve/default.sieve") - except CommandExecutionError as e: - raise SiteError("Failed to compile default.sieve") - - EEGit.add(self, ["/etc/postfix", "/etc/dovecot"], - msg="Installed mail server") - EEService.restart_service(self, 'dovecot') - EEService.reload_service(self, 'postfix') - - if set(EEVariables.ee_mailscanner).issubset(set(apt_packages)): - # Set up Custom amavis configuration - data = dict() - Log.debug(self, "Configuring file /etc/amavis/conf.d" - "/15-content_filter_mode") - ee_amavis = open('/etc/amavis/conf.d/15-content_filter_mode', - encoding='utf-8', mode='w') - self.app.render((data), '15-content_filter_mode.mustache', - out=ee_amavis) - ee_amavis.close() - - # Amavis ViMbadmin configuration - if os.path.isfile("/etc/postfix/mysql/virtual_alias_maps.cf"): - vm_host = os.popen("grep hosts /etc/postfix/mysql/virtual_" - "alias_maps.cf | awk \'{ print $3 }\' |" - " tr -d '\\n'").read() - vm_pass = os.popen("grep password /etc/postfix/mysql/" - "virtual_alias_maps.cf | awk \'{ print " - "$3 }\' | tr -d '\\n'").read() - - data = dict(host=vm_host, password=vm_pass) - vm_config = open('/etc/amavis/conf.d/50-user', - encoding='utf-8', mode='w') - self.app.render((data), '50-user.mustache', out=vm_config) - vm_config.close() - - # Amavis postfix configuration - try: - EEShellExec.cmd_exec(self, "postconf -e \"content_filter =" - " smtp-amavis:[127.0.0.1]:10024\"") - EEShellExec.cmd_exec(self, "sed -i \"s/1 pickup/1 " - " pickup" - "\\n -o content_filter=\\n " - " -o receive_override_options=" - "no_header_body" - "_checks/\" /etc/postfix/master.cf") - except CommandExecutionError as e: - raise SiteError("Failed to update Amavis-Postfix config") - - amavis_master = ("""smtp-amavis unix - - n - 2 smtp - -o smtp_data_done_timeout=1200 - -o smtp_send_xforward_command=yes - -o disable_dns_lookups=yes - -o max_use=20 -127.0.0.1:10025 inet n - n - - smtpd - -o content_filter= - -o smtpd_delay_reject=no - -o smtpd_client_restrictions=permit_mynetworks,reject - -o smtpd_helo_restrictions= - -o smtpd_sender_restrictions= - -o smtpd_recipient_restrictions=permit_mynetworks,reject - -o smtpd_data_restrictions=reject_unauth_pipelining - -o smtpd_end_of_data_restrictions= - -o smtpd_restriction_classes= - -o mynetworks=127.0.0.0/8 - -o smtpd_error_sleep_time=0 - -o smtpd_soft_error_limit=1001 - -o smtpd_hard_error_limit=1000 - -o smtpd_client_connection_count_limit=0 - -o smtpd_client_connection_rate_limit=0 - -o local_header_rewrite_clients=""") - - with open("/etc/postfix/master.cf", - encoding='utf-8', mode='a') as am_config: - am_config.write(amavis_master) - - try: - # Amavis ClamAV configuration - Log.debug(self, "Adding new user clamav amavis") - EEShellExec.cmd_exec(self, "adduser clamav amavis") - Log.debug(self, "Adding new user amavis clamav") - EEShellExec.cmd_exec(self, "adduser amavis clamav") - Log.debug(self, "Setting Privileges to /var/lib/amavis" - "/tmp") - EEFileUtils.chmod(self, "/var/lib/amavis/tmp", 0o755) - - # Update ClamAV database - Log.debug(self, "Updating database") - EEShellExec.cmd_exec(self, "freshclam") - except CommandExecutionError as e: - raise SiteError(" Unable to update ClamAV-Amavis config") - - EEGit.add(self, ["/etc/amavis"], msg="Adding Amavis into Git") - EEService.restart_service(self, 'dovecot') - EEService.reload_service(self, 'postfix') - EEService.restart_service(self, 'amavis') - - if len(packages): - if any('/usr/bin/wp' == x[1] for x in packages): - Log.debug(self, "Setting Privileges to /usr/bin/wp file ") - EEFileUtils.chmod(self, "/usr/bin/wp", 0o775) - - if any('/tmp/pma.tar.gz' == x[1] - for x in packages): - EEExtract.extract(self, '/tmp/pma.tar.gz', '/tmp/') - Log.debug(self, 'Extracting file /tmp/pma.tar.gz to ' - 'location /tmp/') - if not os.path.exists('{0}22222/htdocs/db' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating new directory " - "{0}22222/htdocs/db" - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/db' - .format(EEVariables.ee_webroot)) - shutil.move('/tmp/phpmyadmin-STABLE/', - '{0}22222/htdocs/db/pma/' - .format(EEVariables.ee_webroot)) - shutil.copyfile('{0}22222/htdocs/db/pma/config.sample.inc.php' - .format(EEVariables.ee_webroot), - '{0}22222/htdocs/db/pma/config.inc.php' - .format(EEVariables.ee_webroot)) - Log.debug(self, 'Setting Blowfish Secret Key FOR COOKIE AUTH to ' - '{0}22222/htdocs/db/pma/config.inc.php file ' - .format(EEVariables.ee_webroot)) - blowfish_key = ''.join([random.choice - (string.ascii_letters + string.digits) - for n in range(10)]) - EEFileUtils.searchreplace(self, - '{0}22222/htdocs/db/pma/config.inc.php' - .format(EEVariables.ee_webroot), - "$cfg[\'blowfish_secret\'] = \'\';","$cfg[\'blowfish_secret\'] = \'{0}\';" - .format(blowfish_key)) - Log.debug(self, 'Setting HOST Server For Mysql to ' - '{0}22222/htdocs/db/pma/config.inc.php file ' - .format(EEVariables.ee_webroot)) - EEFileUtils.searchreplace(self, - '{0}22222/htdocs/db/pma/config.inc.php' - .format(EEVariables.ee_webroot), - "$cfg[\'Servers\'][$i][\'host\'] = \'localhost\';","$cfg[\'Servers\'][$i][\'host\'] = \'{0}\';" - .format(EEVariables.ee_mysql_host)) - Log.debug(self, 'Setting Privileges of webroot permission to ' - '{0}22222/htdocs/db/pma file ' - .format(EEVariables.ee_webroot)) - EEFileUtils.chown(self, '{0}22222' - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, - recursive=True) - if any('/tmp/memcache.tar.gz' == x[1] - for x in packages): - Log.debug(self, "Extracting memcache.tar.gz to location" - " {0}22222/htdocs/cache/memcache " - .format(EEVariables.ee_webroot)) - EEExtract.extract(self, '/tmp/memcache.tar.gz', - '{0}22222/htdocs/cache/memcache' - .format(EEVariables.ee_webroot)) - Log.debug(self, "Setting Privileges to " - "{0}22222/htdocs/cache/memcache file" - .format(EEVariables.ee_webroot)) - EEFileUtils.chown(self, '{0}22222' - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, - recursive=True) - - if any('/tmp/webgrind.tar.gz' == x[1] - for x in packages): - Log.debug(self, "Extracting file webgrind.tar.gz to " - "location /tmp/ ") - EEExtract.extract(self, '/tmp/webgrind.tar.gz', '/tmp/') - if not os.path.exists('{0}22222/htdocs/php' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating directroy " - "{0}22222/htdocs/php" - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/php' - .format(EEVariables.ee_webroot)) - shutil.move('/tmp/webgrind-master/', - '{0}22222/htdocs/php/webgrind' - .format(EEVariables.ee_webroot)) - - EEFileUtils.searchreplace(self, "{0}22222/htdocs/php/webgrind/" - "config.php" - .format(EEVariables.ee_webroot), - "/usr/local/bin/dot", "/usr/bin/dot") - EEFileUtils.searchreplace(self, "{0}22222/htdocs/php/webgrind/" - "config.php" - .format(EEVariables.ee_webroot), - "Europe/Copenhagen", - EEVariables.ee_timezone) - - EEFileUtils.searchreplace(self, "{0}22222/htdocs/php/webgrind/" - "config.php" - .format(EEVariables.ee_webroot), - "90", "100") - - Log.debug(self, "Setting Privileges of webroot permission to " - "{0}22222/htdocs/php/webgrind/ file " - .format(EEVariables.ee_webroot)) - EEFileUtils.chown(self, '{0}22222' - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, - recursive=True) - - if any('/tmp/anemometer.tar.gz' == x[1] - for x in packages): - Log.debug(self, "Extracting file anemometer.tar.gz to " - "location /tmp/ ") - EEExtract.extract(self, '/tmp/anemometer.tar.gz', '/tmp/') - if not os.path.exists('{0}22222/htdocs/db/' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating directory") - os.makedirs('{0}22222/htdocs/db/' - .format(EEVariables.ee_webroot)) - shutil.move('/tmp/Anemometer-master', - '{0}22222/htdocs/db/anemometer' - .format(EEVariables.ee_webroot)) - chars = ''.join(random.sample(string.ascii_letters, 8)) - try: - EEShellExec.cmd_exec(self, 'mysql < {0}22222/htdocs/db' - '/anemometer/install.sql' - .format(EEVariables.ee_webroot)) - except CommandExecutionError as e: - raise SiteError("Unable to import Anemometer database") - - EEMysql.execute(self, 'grant select on *.* to \'anemometer\'' - '@\'{0}\' IDENTIFIED' - ' BY \'{1}\''.format(self.app.config.get('mysql', - 'grant-host'),chars)) - Log.debug(self, "grant all on slow-query-log.*" - " to anemometer@root_user IDENTIFIED BY password ") - EEMysql.execute(self, 'grant all on slow_query_log.* to' - '\'anemometer\'@\'{0}\' IDENTIFIED' - ' BY \'{1}\''.format(self.app.config.get( - 'mysql', 'grant-host'), - chars), - errormsg="cannot grant priviledges", log=False) - - # Custom Anemometer configuration - Log.debug(self, "configration Anemometer") - data = dict(host=EEVariables.ee_mysql_host, port='3306', - user='anemometer', password=chars) - ee_anemometer = open('{0}22222/htdocs/db/anemometer' - '/conf/config.inc.php' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='w') - self.app.render((data), 'anemometer.mustache', - out=ee_anemometer) - ee_anemometer.close() - - if any('/usr/bin/pt-query-advisor' == x[1] - for x in packages): - EEFileUtils.chmod(self, "/usr/bin/pt-query-advisor", 0o775) - - if any('/tmp/vimbadmin.tar.gz' == x[1] for x in packages): - # Extract ViMbAdmin - Log.debug(self, "Extracting ViMbAdmin.tar.gz to " - "location /tmp/") - EEExtract.extract(self, '/tmp/vimbadmin.tar.gz', '/tmp/') - if not os.path.exists('{0}22222/htdocs/' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating directory " - "{0}22222/htdocs/" - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/' - .format(EEVariables.ee_webroot)) - shutil.move('/tmp/ViMbAdmin-{0}/' - .format(EEVariables.ee_vimbadmin), - '{0}22222/htdocs/vimbadmin/' - .format(EEVariables.ee_webroot)) - - # Donwload composer and install ViMbAdmin - Log.debug(self, "Downloading composer " - "https://getcomposer.org/installer | php ") - try: - EEShellExec.cmd_exec(self, "cd {0}22222/htdocs" - "/vimbadmin; curl" - " -sS https://getcomposer.org/" - "installer |" - " php".format(EEVariables.ee_webroot)) - Log.debug(self, "Installating of composer") - EEShellExec.cmd_exec(self, "cd {0}22222/htdocs" - "/vimbadmin && " - "php composer.phar install " - "--prefer-dist" - " --no-dev && rm -f {1}22222/htdocs" - "/vimbadmin/composer.phar" - .format(EEVariables.ee_webroot, - EEVariables.ee_webroot)) - except CommandExecutionError as e: - raise SiteError("Failed to setup ViMbAdmin") - - # Configure vimbadmin database - vm_passwd = ''.join(random.sample(string.ascii_letters, 8)) - Log.debug(self, "Creating vimbadmin database if not exist") - EEMysql.execute(self, "create database if not exists" - " vimbadmin") - Log.debug(self, " grant all privileges on `vimbadmin`.* to" - " `vimbadmin`@`{0}` IDENTIFIED BY" - " ' '".format(self.app.config.get('mysql', - 'grant-host'))) - EEMysql.execute(self, "grant all privileges on `vimbadmin`.* " - " to `vimbadmin`@`{0}` IDENTIFIED BY" - " '{1}'".format(self.app.config.get('mysql', - 'grant-host'), vm_passwd), - errormsg="Cannot grant " - "user privileges", log=False) - vm_salt = (''.join(random.sample(string.ascii_letters + - string.ascii_letters, 64))) - - # Custom Vimbadmin configuration by EasyEngine - data = dict(salt=vm_salt, host=EEVariables.ee_mysql_host, - password=vm_passwd, - php_user=EEVariables.ee_php_user) - Log.debug(self, 'Writting the ViMbAdmin configuration to ' - 'file {0}22222/htdocs/vimbadmin/application/' - 'configs/application.ini' - .format(EEVariables.ee_webroot)) - ee_vmb = open('{0}22222/htdocs/vimbadmin/application/' - 'configs/application.ini' - .format(EEVariables.ee_webroot), - encoding='utf-8', mode='w') - self.app.render((data), 'vimbadmin.mustache', - out=ee_vmb) - ee_vmb.close() - - shutil.copyfile("{0}22222/htdocs/vimbadmin/public/" - ".htaccess.dist" - .format(EEVariables.ee_webroot), - "{0}22222/htdocs/vimbadmin/public/" - ".htaccess".format(EEVariables.ee_webroot)) - Log.debug(self, "Executing command " - "{0}22222/htdocs/vimbadmin/bin" - "/doctrine2-cli.php orm:schema-tool:" - "create".format(EEVariables.ee_webroot)) - try: - EEShellExec.cmd_exec(self, "{0}22222/htdocs/vimbadmin" - "/bin/doctrine2-cli.php " - "orm:schema-tool:create" - .format(EEVariables.ee_webroot)) - except CommandExecutionError as e: - raise SiteError("Unable to create ViMbAdmin schema") - - EEFileUtils.chown(self, '{0}22222' - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, - recursive=True) - - # Copy Dovecot and Postfix templates which are depednet on - # Vimbadmin - - if not os.path.exists('/etc/postfix/mysql/'): - Log.debug(self, "Creating directory " - "/etc/postfix/mysql/") - os.makedirs('/etc/postfix/mysql/') - - if EEVariables.ee_mysql_host is "localhost": - data = dict(password=vm_passwd, host="127.0.0.1") - else: - data = dict(password=vm_passwd, - host=EEVariables.ee_mysql_host) - - vm_config = open('/etc/postfix/mysql/virtual_alias_maps.cf', - encoding='utf-8', mode='w') - self.app.render((data), 'virtual_alias_maps.mustache', - out=vm_config) - vm_config.close() - - Log.debug(self, "Writting configuration to " - "/etc/postfix/mysql" - "/virtual_domains_maps.cf file") - vm_config = open('/etc/postfix/mysql/virtual_domains_maps.cf', - encoding='utf-8', mode='w') - self.app.render((data), 'virtual_domains_maps.mustache', - out=vm_config) - vm_config.close() - - Log.debug(self, "Writting configuration to " - "/etc/postfix/mysql" - "/virtual_mailbox_maps.cf file") - vm_config = open('/etc/postfix/mysql/virtual_mailbox_maps.cf', - encoding='utf-8', mode='w') - self.app.render((data), 'virtual_mailbox_maps.mustache', - out=vm_config) - vm_config.close() - - Log.debug(self, "Writting configration" - " to /etc/dovecot/dovecot-sql.conf.ext file ") - vm_config = open('/etc/dovecot/dovecot-sql.conf.ext', - encoding='utf-8', mode='w') - self.app.render((data), 'dovecot-sql-conf.mustache', - out=vm_config) - vm_config.close() - - # If Amavis is going to be installed then configure Vimabadmin - # Amvis settings - if set(EEVariables.ee_mailscanner).issubset(set(apt_packages)): - vm_config = open('/etc/amavis/conf.d/50-user', - encoding='utf-8', mode='w') - self.app.render((data), '50-user.mustache', - out=vm_config) - vm_config.close() - EEService.restart_service(self, 'dovecot') - EEService.reload_service(self, 'nginx') - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - EEService.reload_service(self, 'php5-fpm') - else: - EEService.reload_service(self, 'php5.6-fpm') - if EEAptGet.is_installed(self, 'php7.0-fpm'): - EEService.reload_service(self, 'php7.0-fpm') - self.msg = (self.msg + ["Configure ViMbAdmin:\thttps://{0}:" - "22222/vimbadmin".format(EEVariables.ee_fqdn)] - + ["Security Salt: {0}".format(vm_salt)]) - - if any('/tmp/roundcube.tar.gz' == x[1] for x in packages): - # Extract RoundCubemail - Log.debug(self, "Extracting file /tmp/roundcube.tar.gz " - "to location /tmp/ ") - EEExtract.extract(self, '/tmp/roundcube.tar.gz', '/tmp/') - if not os.path.exists('{0}roundcubemail' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating new directory " - " {0}roundcubemail/" - .format(EEVariables.ee_webroot)) - os.makedirs('{0}roundcubemail/' - .format(EEVariables.ee_webroot)) - shutil.move('/tmp/roundcubemail-{0}/' - .format(EEVariables.ee_roundcube), - '{0}roundcubemail/htdocs' - .format(EEVariables.ee_webroot)) - - #Fix pear install config for trusty - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - EEShellExec.cmd_exec(self, "pear config-set php_dir /usr/share/php") - EEShellExec.cmd_exec(self, "pear config-set doc_dir /lib/php/pear/docs") - EEShellExec.cmd_exec(self, "pear config-set cfg_dir /lib/php/pear/cfg") - EEShellExec.cmd_exec(self, "pear config-set data_dir /lib/php/pear/data") - EEShellExec.cmd_exec(self, "pear config-set test_dir /lib/php/pear/tests") - EEShellExec.cmd_exec(self, "pear config-set www_dir /lib/php/pear/www") - # Install Roundcube depednet pear packages - EEShellExec.cmd_exec(self, "pear install Mail_Mime Net_SMTP" - " Mail_mimeDecode Net_IDNA2-beta " - "Auth_SASL Net_Sieve Crypt_GPG") - - # pear install Mail_Mime Net_SMTP Mail_mimeDecode Net_IDNA2-beta Auth_SASL Net_Sieve Crypt_GPG - - # Configure roundcube database - rc_passwd = ''.join(random.sample(string.ascii_letters, 8)) - Log.debug(self, "Creating Database roundcubemail") - EEMysql.execute(self, "create database if not exists " - " roundcubemail") - Log.debug(self, "grant all privileges" - " on `roundcubemail`.* to " - " `roundcube`@`{0}` IDENTIFIED BY " - "' '".format(self.app.config.get( - 'mysql', 'grant-host'))) - EEMysql.execute(self, "grant all privileges" - " on `roundcubemail`.* to " - " `roundcube`@`{0}` IDENTIFIED BY " - "'{1}'".format(self.app.config.get( - 'mysql', 'grant-host'), - rc_passwd)) - EEShellExec.cmd_exec(self, "mysql roundcubemail < {0}" - "roundcubemail/htdocs/SQL/mysql" - ".initial.sql" - .format(EEVariables.ee_webroot)) - - shutil.copyfile("{0}roundcubemail/htdocs/config/" - "config.inc.php.sample" - .format(EEVariables.ee_webroot), - "{0}roundcubemail/htdocs/config/" - "config.inc.php" - .format(EEVariables.ee_webroot)) - EEShellExec.cmd_exec(self, "sed -i \"s\'mysql://roundcube:" - "pass@localhost/roundcubemail\'mysql://" - "roundcube:{0}@{1}/" - "roundcubemail\'\" {2}roundcubemail" - "/htdocs/config/config." - "inc.php" - .format(rc_passwd, - EEVariables.ee_mysql_host, - EEVariables.ee_webroot)) - - # Sieve plugin configuration in roundcube - EEShellExec.cmd_exec(self, "bash -c \"sed -i \\\"s:\$config\[" - "\'plugins\'\] " - "= array(:\$config\['plugins'\] = " - "array(\\n \'sieverules\',:\\\" " - "{0}roundcubemail/htdocs/config" - .format(EEVariables.ee_webroot) - + "/config.inc.php\"") - EEShellExec.cmd_exec(self, "echo \"\$config['sieverules_port']" - "=4190;\" >> {0}roundcubemail" - .format(EEVariables.ee_webroot) - + "/htdocs/config/config.inc.php") - - data = dict(site_name='webmail', www_domain='webmail', - static=False, - basic=True, wp=False, w3tc=False, wpfc=False, - wpsc=False, multisite=False, wpsubdir=False, - webroot=EEVariables.ee_webroot, ee_db_name='', - ee_db_user='', ee_db_pass='', ee_db_host='', - rc=True) - - Log.debug(self, 'Writting the nginx configuration for ' - 'RoundCubemail') - ee_rc = open('/etc/nginx/sites-available/webmail', - encoding='utf-8', mode='w') - self.app.render((data), 'virtualconf.mustache', - out=ee_rc) - ee_rc.close() - - # Create Symbolic link for webmail - EEFileUtils.create_symlink(self, ['/etc/nginx/sites-available' - '/webmail', - '/etc/nginx/sites-enabled/' - 'webmail']) - # Create log folder and softlinks - if not os.path.exists('{0}roundcubemail/logs' - .format(EEVariables.ee_webroot)): - os.makedirs('{0}roundcubemail/logs' - .format(EEVariables.ee_webroot)) - - EEFileUtils.create_symlink(self, ['/var/log/nginx/' - 'webmail.access.log', - '{0}roundcubemail/' - 'logs/access.log' - .format(EEVariables.ee_webroot)]) - - EEFileUtils.create_symlink(self, ['/var/log/nginx/' - 'webmail.error.log', - '{0}roundcubemail/' - 'logs/error.log' - .format(EEVariables.ee_webroot)]) - # Remove roundcube installer - EEService.reload_service(self, 'nginx') - EEFileUtils.remove(self, ["{0}roundcubemail/htdocs/installer" - .format(EEVariables.ee_webroot)]) - EEFileUtils.chown(self, '{0}roundcubemail' - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, - recursive=True) - - if any('/tmp/pra.tar.gz' == x[1] - for x in packages): - Log.debug(self, 'Extracting file /tmp/pra.tar.gz to ' - 'loaction /tmp/') - EEExtract.extract(self, '/tmp/pra.tar.gz', '/tmp/') - if not os.path.exists('{0}22222/htdocs/cache/redis' - .format(EEVariables.ee_webroot)): - Log.debug(self, "Creating new directory " - "{0}22222/htdocs/cache/redis" - .format(EEVariables.ee_webroot)) - os.makedirs('{0}22222/htdocs/cache/redis' - .format(EEVariables.ee_webroot)) - shutil.move('/tmp/phpRedisAdmin-master/', - '{0}22222/htdocs/cache/redis/phpRedisAdmin' - .format(EEVariables.ee_webroot)) - - Log.debug(self, 'Extracting file /tmp/predis.tar.gz to ' - 'loaction /tmp/') - EEExtract.extract(self, '/tmp/predis.tar.gz', '/tmp/') - shutil.move('/tmp/predis-1.0.1/', - '{0}22222/htdocs/cache/redis/phpRedisAdmin/vendor' - .format(EEVariables.ee_webroot)) - - Log.debug(self, 'Setting Privileges of webroot permission to ' - '{0}22222/htdocs/cache/ file ' - .format(EEVariables.ee_webroot)) - EEFileUtils.chown(self, '{0}22222' - .format(EEVariables.ee_webroot), - EEVariables.ee_php_user, - EEVariables.ee_php_user, - recursive=True) - - @expose(help="Install packages") - def install(self, packages=[], apt_packages=[], disp_msg=True): - """Start installation of packages""" - if self.app.pargs.pagespeed: - Log.error(self, "Pagespeed support has been dropped since EasyEngine v3.6.0",False) - Log.error(self, "Please run command again without `--pagespeed`",False) - Log.error(self, "For more details, read - https://easyengine.io/blog/disabling-pagespeed/") - self.msg = [] - try: - # Default action for stack installation - if ((not self.app.pargs.web) and (not self.app.pargs.admin) and - (not self.app.pargs.mail) and (not self.app.pargs.nginx) and - (not self.app.pargs.php) and (not self.app.pargs.mysql) and - (not self.app.pargs.postfix) and (not self.app.pargs.wpcli) and - (not self.app.pargs.phpmyadmin) and (not self.app.pargs.hhvm) - and (not self.app.pargs.pagespeed) and - (not self.app.pargs.adminer) and (not self.app.pargs.utils) and - (not self.app.pargs.mailscanner) and (not self.app.pargs.all) - and (not self.app.pargs.redis) and - (not self.app.pargs.phpredisadmin) and (not self.app.pargs.php7)): - self.app.pargs.web = True - self.app.pargs.admin = True - - if self.app.pargs.all: - self.app.pargs.web = True - self.app.pargs.admin = True - self.app.pargs.mail = True - - if self.app.pargs.web: - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.wpcli = True - self.app.pargs.postfix = True - - if self.app.pargs.admin: - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.adminer = True - self.app.pargs.phpmyadmin = True - self.app.pargs.utils = True - - if self.app.pargs.mail: - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.postfix = True - - if not EEAptGet.is_installed(self, 'dovecot-core'): - check_fqdn(self, - os.popen("hostname -f | tr -d '\n'").read()) - Log.debug(self, "Setting apt_packages variable for mail") - apt_packages = apt_packages + EEVariables.ee_mail - - # https://github.com/EasyEngine/ViMbAdmin/archive/3.0.13.tar.gz - packages = packages + [["https://github.com/opensolutions/" - "ViMbAdmin/archive/{0}.tar.gz" - .format(EEVariables.ee_vimbadmin), - "/tmp/vimbadmin.tar.gz", - "ViMbAdmin"], - ["https://github.com/roundcube/" - "roundcubemail/releases/download/" - "{0}/roundcubemail-{0}.tar.gz" - .format(EEVariables.ee_roundcube), - "/tmp/roundcube.tar.gz", - "Roundcube"]] - - if EEVariables.ee_ram > 1024: - self.app.pargs.mailscanner = True - else: - Log.info(self, "System RAM is less than 1GB\nMail " - "scanner packages are not going to install" - " automatically") - else: - Log.info(self, "Mail server is already installed") - - - if self.app.pargs.redis: - if not EEAptGet.is_installed(self, 'redis-server'): - apt_packages = apt_packages + EEVariables.ee_redis - self.app.pargs.php = True - else: - Log.info(self, "Redis already installed") - - if self.app.pargs.nginx: - Log.debug(self, "Setting apt_packages variable for Nginx") - - if not (EEAptGet.is_installed(self, 'nginx-custom')): - if not (EEAptGet.is_installed(self, 'nginx-plus') or EEAptGet.is_installed(self, 'nginx')): - apt_packages = apt_packages + EEVariables.ee_nginx - else: - if EEAptGet.is_installed(self, 'nginx-plus'): - Log.info(self, "NGINX PLUS Detected ...") - apt = ["nginx-plus"] + EEVariables.ee_nginx - self.post_pref(apt, packages) - elif EEAptGet.is_installed(self, 'nginx'): - Log.info(self, "EasyEngine detected a previously installed Nginx package. " - "It may or may not have required modules. " - "\nIf you need help, please create an issue at https://github.com/EasyEngine/easyengine/issues/ \n") - apt = ["nginx"] + EEVariables.ee_nginx - self.post_pref(apt, packages) - else: - Log.debug(self, "Nginx Stable already installed") - - if self.app.pargs.php: - Log.debug(self, "Setting apt_packages variable for PHP") - if not (EEAptGet.is_installed(self, 'php5-fpm') or EEAptGet.is_installed(self, 'php5.6-fpm')): - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - apt_packages = apt_packages + EEVariables.ee_php5_6 + EEVariables.ee_php_extra - else: - apt_packages = apt_packages + EEVariables.ee_php - else: - Log.debug(self, "PHP already installed") - Log.info(self, "PHP already installed") - - #PHP 7.0 for Debian (jessie+) - if self.app.pargs.php7 and EEVariables.ee_platform_distro == 'debian': - if (EEVariables.ee_platform_codename == 'jessie'): - Log.debug(self, "Setting apt_packages variable for PHP 7.0") - if not EEAptGet.is_installed(self, 'php7.0-fpm') : - apt_packages = apt_packages + EEVariables.ee_php7_0 - if not EEAptGet.is_installed(self, 'php5-fpm'): - apt_packages = apt_packages + EEVariables.ee_php - else: - Log.debug(self, "PHP 7.0 already installed") - Log.info(self, "PHP 7.0 already installed") - else: - Log.debug(self, "PHP 7.0 Not Available for your Distribution") - Log.info(self, "PHP 7.0 Not Available for your Distribution") - - #PHP 7.0 for Ubuntu - if self.app.pargs.php7 and not EEVariables.ee_platform_distro == 'debian': - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - Log.debug(self, "Setting apt_packages variable for PHP 7.0") - if not EEAptGet.is_installed(self, 'php7.0-fpm') : - apt_packages = apt_packages + EEVariables.ee_php7_0 + EEVariables.ee_php_extra - if not EEAptGet.is_installed(self, 'php5.6-fpm'): - apt_packages = apt_packages + EEVariables.ee_php5_6 + EEVariables.ee_php_extra - else: - Log.debug(self, "PHP 7.0 already installed") - Log.info(self, "PHP 7.0 already installed") - else: - Log.debug(self, "PHP 7.0 Not Available for your Distribution") - Log.info(self, "PHP 7.0 Not Available for your Distribution") - - - if self.app.pargs.hhvm: - Log.debug(self, "Setting apt packages variable for HHVM") - if platform.architecture()[0] is '32bit': - Log.error(self, "HHVM is not supported by 32bit system") - if not EEAptGet.is_installed(self, 'hhvm'): - apt_packages = apt_packages + EEVariables.ee_hhvm - else: - Log.debug(self, "HHVM already installed") - Log.info(self, "HHVM already installed") - - if self.app.pargs.mysql: - Log.debug(self, "Setting apt_packages variable for MySQL") - if not EEShellExec.cmd_exec(self, "mysqladmin ping"): - apt_packages = apt_packages + EEVariables.ee_mysql - packages = packages + [["https://raw." - "githubusercontent.com/" - "major/MySQLTuner-perl" - "/master/mysqltuner.pl", - "/usr/bin/mysqltuner", - "MySQLTuner"]] - - else: - Log.debug(self, "MySQL connection is already alive") - Log.info(self, "MySQL connection is already alive") - if self.app.pargs.postfix: - Log.debug(self, "Setting apt_packages variable for Postfix") - if not EEAptGet.is_installed(self, 'postfix'): - apt_packages = apt_packages + EEVariables.ee_postfix - else: - Log.debug(self, "Postfix is already installed") - Log.info(self, "Postfix is already installed") - if self.app.pargs.wpcli: - Log.debug(self, "Setting packages variable for WP-CLI") - if not EEShellExec.cmd_exec(self, "which wp"): - packages = packages + [["https://github.com/wp-cli/wp-cli/" - "releases/download/v{0}/" - "wp-cli-{0}.phar" - "".format(EEVariables.ee_wp_cli), - "/usr/bin/wp", - "WP-CLI"]] - else: - Log.debug(self, "WP-CLI is already installed") - Log.info(self, "WP-CLI is already installed") - if self.app.pargs.phpmyadmin: - Log.debug(self, "Setting packages varible for phpMyAdmin ") - packages = packages + [["https://github.com/phpmyadmin/" - "phpmyadmin/archive/STABLE.tar.gz", - "/tmp/pma.tar.gz", "phpMyAdmin"]] - - if self.app.pargs.phpredisadmin: - Log.debug(self, "Setting packages varible for phpRedisAdmin") - packages = packages + [["https://github.com/ErikDubbelboer/" - "phpRedisAdmin/archive/master.tar.gz", - "/tmp/pra.tar.gz","phpRedisAdmin"], - ["https://github.com/nrk/predis/" - "archive/v1.0.1.tar.gz", - "/tmp/predis.tar.gz", "Predis"]] - - if self.app.pargs.adminer: - Log.debug(self, "Setting packages variable for Adminer ") - packages = packages + [["https://www.adminer.org/static/download/" - "{0}/adminer-{0}.php" - "".format(EEVariables.ee_adminer), - "{0}22222/" - "htdocs/db/adminer/index.php" - .format(EEVariables.ee_webroot), - "Adminer"]] - - if self.app.pargs.mailscanner: - if not EEAptGet.is_installed(self, 'amavisd-new'): - if (EEAptGet.is_installed(self, 'dovecot-core') or - self.app.pargs.mail): - apt_packages = (apt_packages + - EEVariables.ee_mailscanner) - else: - Log.error(self, "Failed to find installed Dovecot") - else: - Log.error(self, "Mail scanner already installed") - - if self.app.pargs.utils: - Log.debug(self, "Setting packages variable for utils") - packages = packages + [["https://storage.googleapis.com/google-code-archive-downloads/" - "v2/code.google.com/phpmemcacheadmin/" - "phpMemcachedAdmin-1.2.2-r262.tar.gz", '/tmp/memcache.tar.gz', - 'phpMemcachedAdmin'], - ["https://raw.githubusercontent.com" - "/rtCamp/eeadmin/master/cache/nginx/" - "clean.php", - "{0}22222/htdocs/cache/" - "nginx/clean.php" - .format(EEVariables.ee_webroot), - "clean.php"], - ["https://raw.github.com/rlerdorf/" - "opcache-status/master/opcache.php", - "{0}22222/htdocs/cache/" - "opcache/opcache.php" - .format(EEVariables.ee_webroot), - "opcache.php"], - ["https://raw.github.com/amnuts/" - "opcache-gui/master/index.php", - "{0}22222/htdocs/" - "cache/opcache/opgui.php" - .format(EEVariables.ee_webroot), - "Opgui"], - ["https://gist.github.com/ck-on/4959032" - "/raw/0b871b345fd6cfcd6d2be030c1f33d1" - "ad6a475cb/ocp.php", - "{0}22222/htdocs/cache/" - "opcache/ocp.php" - .format(EEVariables.ee_webroot), - "OCP.php"], - ["https://github.com/jokkedk/webgrind/" - "archive/master.tar.gz", - '/tmp/webgrind.tar.gz', 'Webgrind'], - ["http://bazaar.launchpad.net/~" - "percona-toolkit-dev/percona-toolkit/" - "2.1/download/head:/ptquerydigest-" - "20110624220137-or26tn4" - "expb9ul2a-16/pt-query-digest", - "/usr/bin/pt-query-advisor", - "pt-query-advisor"], - ["https://github.com/box/Anemometer/" - "archive/master.tar.gz", - '/tmp/anemometer.tar.gz', 'Anemometer'] - ] - except Exception as e: - pass - - if len(apt_packages) or len(packages): - Log.debug(self, "Calling pre_pref") - self.pre_pref(apt_packages) - if len(apt_packages): - EESwap.add(self) - Log.info(self, "Updating apt-cache, please wait...") - EEAptGet.update(self) - Log.info(self, "Installing packages, please wait...") - EEAptGet.install(self, apt_packages) - if len(packages): - Log.debug(self, "Downloading following: {0}".format(packages)) - EEDownload.download(self, packages) - Log.debug(self, "Calling post_pref") - self.post_pref(apt_packages, packages) - if 'redis-server' in apt_packages: - # set redis.conf parameter - # set maxmemory 10% for ram below 512MB and 20% for others - # set maxmemory-policy allkeys-lru - if os.path.isfile("/etc/redis/redis.conf"): - if EEVariables.ee_ram < 512: - Log.debug(self, "Setting maxmemory variable to {0} in redis.conf" - .format(int(EEVariables.ee_ram*1024*1024*0.1))) - EEShellExec.cmd_exec(self, "sed -i 's/# maxmemory /maxmemory {0}/' /etc/redis/redis.conf" - .format(int(EEVariables.ee_ram*1024*1024*0.1))) - Log.debug(self, "Setting maxmemory-policy variable to allkeys-lru in redis.conf") - EEShellExec.cmd_exec(self, "sed -i 's/# maxmemory-policy.*/maxmemory-policy allkeys-lru/' " - "/etc/redis/redis.conf") - EEService.restart_service(self, 'redis-server') - else: - Log.debug(self, "Setting maxmemory variable to {0} in redis.conf" - .format(int(EEVariables.ee_ram*1024*1024*0.2))) - EEShellExec.cmd_exec(self, "sed -i 's/# maxmemory /maxmemory {0}/' /etc/redis/redis.conf" - .format(int(EEVariables.ee_ram*1024*1024*0.2))) - Log.debug(self, "Setting maxmemory-policy variable to allkeys-lru in redis.conf") - EEShellExec.cmd_exec(self, "sed -i 's/# maxmemory-policy.*/maxmemory-policy allkeys-lru/' " - "/etc/redis/redis.conf") - EEService.restart_service(self, 'redis-server') - if disp_msg: - if len(self.msg): - for msg in self.msg: - Log.info(self, Log.ENDC + msg) - Log.info(self, "Successfully installed packages") - else: - return self.msg - - @expose(help="Remove packages") - def remove(self): - """Start removal of packages""" - apt_packages = [] - packages = [] - - if self.app.pargs.pagespeed: - Log.error(self, "Pagespeed support has been dropped since EasyEngine v3.6.0",False) - Log.error(self, "Please run command again without `--pagespeed`",False) - Log.error(self, "For more details, read - https://easyengine.io/blog/disabling-pagespeed/") - - - # Default action for stack remove - if ((not self.app.pargs.web) and (not self.app.pargs.admin) and - (not self.app.pargs.mail) and (not self.app.pargs.nginx) and - (not self.app.pargs.php) and (not self.app.pargs.php7) and (not self.app.pargs.mysql) and - (not self.app.pargs.postfix) and (not self.app.pargs.wpcli) and - (not self.app.pargs.phpmyadmin) and (not self.app.pargs.hhvm) and - (not self.app.pargs.adminer) and (not self.app.pargs.utils) and - (not self.app.pargs.mailscanner) and (not self.app.pargs.all) and - (not self.app.pargs.pagespeed) and (not self.app.pargs.redis) and - (not self.app.pargs.phpredisadmin)): - self.app.pargs.web = True - self.app.pargs.admin = True - - if self.app.pargs.all: - self.app.pargs.web = True - self.app.pargs.admin = True - self.app.pargs.mail = True - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - self.app.pargs.php7 = True - - if self.app.pargs.web: - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.wpcli = True - self.app.pargs.postfix = True - - if self.app.pargs.admin: - self.app.pargs.adminer = True - self.app.pargs.phpmyadmin = True - self.app.pargs.utils = True - - if self.app.pargs.mail: - Log.debug(self, "Removing mail server packages") - apt_packages = apt_packages + EEVariables.ee_mail - apt_packages = apt_packages + EEVariables.ee_mailscanner - packages = packages + ["{0}22222/htdocs/vimbadmin" - .format(EEVariables.ee_webroot), - "{0}roundcubemail" - .format(EEVariables.ee_webroot)] - if EEShellExec.cmd_exec(self, "mysqladmin ping"): - EEMysql.execute(self, "drop database IF EXISTS vimbadmin") - EEMysql.execute(self, "drop database IF EXISTS roundcubemail") - - if self.app.pargs.mailscanner: - apt_packages = (apt_packages + EEVariables.ee_mailscanner) - - if self.app.pargs.pagespeed: - Log.debug(self, "Removing packages varible of Pagespeed") - packages = packages + ['/etc/nginx/conf.d/pagespeed.conf'] - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom'): - Log.debug(self, "Removing apt_packages variable of Nginx") - apt_packages = apt_packages + EEVariables.ee_nginx - else: - Log.error(self,"Cannot Remove! Nginx Stable version not found.") - if self.app.pargs.php: - Log.debug(self, "Removing apt_packages variable of PHP") - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - apt_packages = apt_packages + EEVariables.ee_php5_6 - if not EEAptGet.is_installed(self, 'php7.0-fpm'): - apt_packages = apt_packages + EEVariables.ee_php_extra - else: - apt_packages = apt_packages + EEVariables.ee_php - - #PHP7.0 for debian(jessie+) - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'jessie'): - Log.debug(self, "Removing apt_packages variable of PHP 7.0") - apt_packages = apt_packages + EEVariables.ee_php7_0 - if not EEAptGet.is_installed(self, 'php5-fpm'): - apt_packages = apt_packages + EEVariables.ee_php_extra - else: - Log.info(self,"PHP 7.0 not supported.") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - Log.debug(self, "Removing apt_packages variable of PHP 7.0") - apt_packages = apt_packages + EEVariables.ee_php7_0 - if not EEAptGet.is_installed(self, 'php5.6-fpm'): - apt_packages = apt_packages + EEVariables.ee_php_extra - else: - Log.info(self,"PHP 7.0 not supported.") - - if self.app.pargs.hhvm: - if EEAptGet.is_installed(self, 'hhvm'): - Log.debug(self, "Removing apt_packages variable of HHVM") - apt_packages = apt_packages + EEVariables.ee_hhvm - if self.app.pargs.redis: - Log.debug(self, "Remove apt_packages variable of Redis") - apt_packages = apt_packages + EEVariables.ee_redis - if self.app.pargs.mysql: - Log.debug(self, "Removing apt_packages variable of MySQL") - apt_packages = apt_packages + EEVariables.ee_mysql - packages = packages + ['/usr/bin/mysqltuner'] - if self.app.pargs.postfix: - Log.debug(self, "Removing apt_packages variable of Postfix") - apt_packages = apt_packages + EEVariables.ee_postfix - if self.app.pargs.wpcli: - Log.debug(self, "Removing package variable of WPCLI ") - if os.path.isfile('/usr/bin/wp'): - packages = packages + ['/usr/bin/wp'] - else: - Log.warn(self, "WP-CLI is not installed with EasyEngine") - if self.app.pargs.phpmyadmin: - Log.debug(self, "Removing package variable of phpMyAdmin ") - packages = packages + ['{0}22222/htdocs/db/pma' - .format(EEVariables.ee_webroot)] - if self.app.pargs.phpredisadmin: - Log.debug(self, "Removing package variable of phpRedisAdmin ") - packages = packages + ['{0}22222/htdocs/cache/redis/phpRedisAdmin' - .format(EEVariables.ee_webroot)] - if self.app.pargs.adminer: - Log.debug(self, "Removing package variable of Adminer ") - packages = packages + ['{0}22222/htdocs/db/adminer' - .format(EEVariables.ee_webroot)] - if self.app.pargs.utils: - Log.debug(self, "Removing package variable of utils ") - packages = packages + ['{0}22222/htdocs/php/webgrind/' - .format(EEVariables.ee_webroot), - '{0}22222/htdocs/cache/opcache' - .format(EEVariables.ee_webroot), - '{0}22222/htdocs/cache/nginx/' - 'clean.php'.format(EEVariables.ee_webroot), - '{0}22222/htdocs/cache/memcache' - .format(EEVariables.ee_webroot), - '/usr/bin/pt-query-advisor', - '{0}22222/htdocs/db/anemometer' - .format(EEVariables.ee_webroot)] - - if len(packages) or len(apt_packages): - ee_prompt = input('Are you sure you to want to' - ' remove from server.' - '\nPackage configuration will remain' - ' on server after this operation.\n' - 'Any answer other than ' - '"yes" will be stop this' - ' operation : ') - - if ee_prompt == 'YES' or ee_prompt == 'yes': - - if (set(["nginx-custom"]).issubset(set(apt_packages))) : - EEService.stop_service(self, 'nginx') - - if len(packages): - EEFileUtils.remove(self, packages) - EEAptGet.auto_remove(self) - - if len(apt_packages): - Log.debug(self, "Removing apt_packages") - Log.info(self, "Removing packages, please wait...") - EEAptGet.remove(self, apt_packages) - EEAptGet.auto_remove(self) - - - Log.info(self, "Successfully removed packages") - - #Added for Ondrej Repo missing package Fix - if self.app.pargs.php7: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - Log.info(self, "PHP5.6-fpm found on system.") - Log.info(self, "Verifying and installing missing packages,") - EEShellExec.cmd_exec(self, "apt-get install -y php-memcached php-igbinary") - - - @expose(help="Purge packages") - def purge(self): - """Start purging of packages""" - apt_packages = [] - packages = [] - - if self.app.pargs.pagespeed: - Log.error(self, "Pagespeed support has been dropped since EasyEngine v3.6.0",False) - Log.error(self, "Please run command again without `--pagespeed`",False) - Log.error(self, "For more details, read - https://easyengine.io/blog/disabling-pagespeed/") - - # Default action for stack purge - if ((not self.app.pargs.web) and (not self.app.pargs.admin) and - (not self.app.pargs.mail) and (not self.app.pargs.nginx) and - (not self.app.pargs.php) and (not self.app.pargs.php7) and (not self.app.pargs.mysql) and - (not self.app.pargs.postfix) and (not self.app.pargs.wpcli) and - (not self.app.pargs.phpmyadmin) and (not self.app.pargs.hhvm) and - (not self.app.pargs.adminer) and (not self.app.pargs.utils) and - (not self.app.pargs.mailscanner) and (not self.app.pargs.all) and - (not self.app.pargs.pagespeed) and (not self.app.pargs.redis) and - (not self.app.pargs.phpredisadmin)): - self.app.pargs.web = True - self.app.pargs.admin = True - - if self.app.pargs.all: - self.app.pargs.web = True - self.app.pargs.admin = True - self.app.pargs.mail = True - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - self.app.pargs.php7 = True - - if self.app.pargs.web: - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.wpcli = True - self.app.pargs.postfix = True - - if self.app.pargs.admin: - self.app.pargs.adminer = True - self.app.pargs.phpmyadmin = True - self.app.pargs.utils = True - - if self.app.pargs.mail: - Log.debug(self, "Removing mail server packages") - apt_packages = apt_packages + EEVariables.ee_mail - apt_packages = apt_packages + EEVariables.ee_mailscanner - packages = packages + ["{0}22222/htdocs/vimbadmin" - .format(EEVariables.ee_webroot), - "{0}roundcubemail" - .format(EEVariables.ee_webroot)] - if EEShellExec.cmd_exec(self, "mysqladmin ping"): - EEMysql.execute(self, "drop database IF EXISTS vimbadmin") - EEMysql.execute(self, "drop database IF EXISTS roundcubemail") - - if self.app.pargs.mailscanner: - apt_packages = (apt_packages + EEVariables.ee_mailscanner) - - if self.app.pargs.pagespeed: - Log.debug(self, "Purge packages varible of Pagespeed") - packages = packages + ['/etc/nginx/conf.d/pagespeed.conf'] - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom'): - Log.debug(self, "Purge apt_packages variable of Nginx") - apt_packages = apt_packages + EEVariables.ee_nginx - else: - Log.error(self,"Cannot Purge! Nginx Stable version not found.") - if self.app.pargs.php: - Log.debug(self, "Purge apt_packages variable PHP") - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - apt_packages = apt_packages + EEVariables.ee_php5_6 - if not EEAptGet.is_installed(self, 'php7.0-fpm'): - apt_packages = apt_packages + EEVariables.ee_php_extra - else: - apt_packages = apt_packages + EEVariables.ee_php - - #For debian --php7 - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'jessie'): - Log.debug(self, "Removing apt_packages variable of PHP 7.0") - apt_packages = apt_packages + EEVariables.ee_php7_0 - if not EEAptGet.is_installed(self, 'php5-fpm'): - apt_packages = apt_packages + EEVariables.ee_php_extra - else: - Log.info(self,"PHP 7.0 not supported.") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - Log.debug(self, "Removing apt_packages variable of PHP 7.0") - apt_packages = apt_packages + EEVariables.ee_php7_0 - if not EEAptGet.is_installed(self, 'php5.6-fpm'): - apt_packages = apt_packages + EEVariables.ee_php_extra - else: - Log.info(self,"PHP 7.0 not supported.") - if self.app.pargs.hhvm: - if EEAptGet.is_installed(self, 'hhvm'): - Log.debug(self, "Purge apt_packages varible of HHVM") - apt_packages = apt_packages + EEVariables.ee_hhvm - if self.app.pargs.redis: - Log.debug(self, "Purge apt_packages variable of Redis") - apt_packages = apt_packages + EEVariables.ee_redis - if self.app.pargs.mysql: - Log.debug(self, "Purge apt_packages variable MySQL") - apt_packages = apt_packages + EEVariables.ee_mysql - packages = packages + ['/usr/bin/mysqltuner'] - if self.app.pargs.postfix: - Log.debug(self, "Purge apt_packages variable PostFix") - apt_packages = apt_packages + EEVariables.ee_postfix - if self.app.pargs.wpcli: - Log.debug(self, "Purge package variable WPCLI") - if os.path.isfile('/usr/bin/wp'): - packages = packages + ['/usr/bin/wp'] - else: - Log.warn(self, "WP-CLI is not installed with EasyEngine") - if self.app.pargs.phpmyadmin: - packages = packages + ['{0}22222/htdocs/db/pma'. - format(EEVariables.ee_webroot)] - Log.debug(self, "Purge package variable phpMyAdmin") - if self.app.pargs.phpredisadmin: - Log.debug(self, "Removing package variable of phpRedisAdmin ") - packages = packages + ['{0}22222/htdocs/cache/redis/phpRedisAdmin' - .format(EEVariables.ee_webroot)] - if self.app.pargs.adminer: - Log.debug(self, "Purge package variable Adminer") - packages = packages + ['{0}22222/htdocs/db/adminer' - .format(EEVariables.ee_webroot)] - if self.app.pargs.utils: - Log.debug(self, "Purge package variable utils") - packages = packages + ['{0}22222/htdocs/php/webgrind/' - .format(EEVariables.ee_webroot), - '{0}22222/htdocs/cache/opcache' - .format(EEVariables.ee_webroot), - '{0}22222/htdocs/cache/nginx/' - 'clean.php'.format(EEVariables.ee_webroot), - '{0}22222/htdocs/cache/memcache' - .format(EEVariables.ee_webroot), - '/usr/bin/pt-query-advisor', - '{0}22222/htdocs/db/anemometer' - .format(EEVariables.ee_webroot) - ] - - if len(packages) or len(apt_packages): - ee_prompt = input('Are you sure you to want to purge ' - 'from server ' - 'along with their configuration' - ' packages,\nAny answer other than ' - '"yes" will be stop this ' - 'operation :') - - if ee_prompt == 'YES' or ee_prompt == 'yes': - - if (set(["nginx-custom"]).issubset(set(apt_packages))) : - EEService.stop_service(self, 'nginx') - - if len(apt_packages): - Log.info(self, "Purging packages, please wait...") - EEAptGet.remove(self, apt_packages, purge=True) - EEAptGet.auto_remove(self) - - if len(packages): - EEFileUtils.remove(self, packages) - EEAptGet.auto_remove(self) - - - Log.info(self, "Successfully purged packages") - - #Added for php Ondrej repo missing package fix - if self.app.pargs.php7: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - Log.info(self, "PHP5.6-fpm found on system.") - Log.info(self, "Verifying and installing missing packages,") - EEShellExec.cmd_exec(self, "apt-get install -y php-memcached php-igbinary") - - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EEStackController) - handler.register(EEStackStatusController) - handler.register(EEStackMigrateController) - handler.register(EEStackUpgradeController) - - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_stack_hook) diff --git a/ee/cli/plugins/stack_migrate.py b/ee/cli/plugins/stack_migrate.py deleted file mode 100644 index 05ed62d69..000000000 --- a/ee/cli/plugins/stack_migrate.py +++ /dev/null @@ -1,126 +0,0 @@ -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.mysql import EEMysql -from ee.core.logging import Log -from ee.core.variables import EEVariables -from ee.core.aptget import EEAptGet -from ee.core.shellexec import EEShellExec -from ee.core.apt_repo import EERepo -from ee.core.services import EEService -import configparser -import os - - -class EEStackMigrateController(CementBaseController): - class Meta: - label = 'migrate' - stacked_on = 'stack' - stacked_type = 'nested' - description = ('Migrate stack safely') - arguments = [ - (['--mariadb'], - dict(help="Migrate database to MariaDB", - action='store_true')), - ] - - @expose(hide=True) - def migrate_mariadb(self): - # Backup all database - EEMysql.backupAll(self) - - # Add MariaDB repo - Log.info(self, "Adding repository for MariaDB, please wait...") - - mysql_pref = ("Package: *\nPin: origin sfo1.mirrors.digitalocean.com" - "\nPin-Priority: 1000\n") - with open('/etc/apt/preferences.d/' - 'MariaDB.pref', 'w') as mysql_pref_file: - mysql_pref_file.write(mysql_pref) - - EERepo.add(self, repo_url=EEVariables.ee_mysql_repo) - Log.debug(self, 'Adding key for {0}' - .format(EEVariables.ee_mysql_repo)) - EERepo.add_key(self, '0xcbcb082a1bb943db', - keyserver="keyserver.ubuntu.com") - - config = configparser.ConfigParser() - if os.path.exists('/etc/mysql/conf.d/my.cnf'): - config.read('/etc/mysql/conf.d/my.cnf') - else: - config.read(os.path.expanduser("~")+'/.my.cnf') - - try: - chars = config['client']['password'] - except Exception as e: - Log.error(self, "Error: process exited with error %s" - % e) - - Log.debug(self, "Pre-seeding MariaDB") - Log.debug(self, "echo \"mariadb-server-10.0 " - "mysql-server/root_password " - "password \" | " - "debconf-set-selections") - EEShellExec.cmd_exec(self, "echo \"mariadb-server-10.0 " - "mysql-server/root_password " - "password {chars}\" | " - "debconf-set-selections" - .format(chars=chars), - log=False) - Log.debug(self, "echo \"mariadb-server-10.0 " - "mysql-server/root_password_again " - "password \" | " - "debconf-set-selections") - EEShellExec.cmd_exec(self, "echo \"mariadb-server-10.0 " - "mysql-server/root_password_again " - "password {chars}\" | " - "debconf-set-selections" - .format(chars=chars), - log=False) - - # Install MariaDB - apt_packages = EEVariables.ee_mysql - - # If PHP is installed then install php5-mysql - if EEAptGet.is_installed(self, "php5-fpm"): - apt_packages = apt_packages + ["php5-mysql"] - - # If mail server is installed then install dovecot-sql and postfix-sql - if EEAptGet.is_installed(self, "dovecot-core"): - apt_packages = apt_packages + ["dovecot-mysql", "postfix-mysql", - "libclass-dbi-mysql-perl"] - - Log.info(self, "Updating apt-cache, please wait...") - EEAptGet.update(self) - Log.info(self, "Installing MariaDB, please wait...") - EEAptGet.remove(self, ["mysql-common", "libmysqlclient18"]) - EEAptGet.auto_remove(self) - EEAptGet.install(self, apt_packages) - - # Restart dovecot and postfix if installed - if EEAptGet.is_installed(self, "dovecot-core"): - EEService.restart_service(self, 'dovecot') - EEService.restart_service(self, 'postfix') - - @expose(hide=True) - def default(self): - if ((not self.app.pargs.mariadb)): - self.app.args.print_help() - if self.app.pargs.mariadb: - if EEVariables.ee_mysql_host is not "localhost": - Log.error(self, "Remote MySQL found, EasyEngine will not " - "install MariaDB") - - if EEShellExec.cmd_exec(self, "mysqladmin ping") and (not - EEAptGet.is_installed(self, 'mariadb-server')): - - Log.info(self, "If your database size is big, " - "migration may take some time.") - Log.info(self, "During migration non nginx-cached parts of " - "your site may remain down") - start_migrate = input("Type \"mariadb\" to continue:") - if start_migrate != "mariadb": - Log.error(self, "Not starting migration") - self.migrate_mariadb() - else: - Log.error(self, "Your current MySQL is not alive or " - "you allready installed MariaDB") diff --git a/ee/cli/plugins/stack_services.py b/ee/cli/plugins/stack_services.py deleted file mode 100644 index acb0cf1a2..000000000 --- a/ee/cli/plugins/stack_services.py +++ /dev/null @@ -1,475 +0,0 @@ -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.services import EEService -from ee.core.logging import Log -from ee.core.variables import EEVariables -from ee.core.aptget import EEAptGet - - -class EEStackStatusController(CementBaseController): - class Meta: - label = 'stack_services' - stacked_on = 'stack' - stacked_type = 'embedded' - description = 'Get status of stack' - arguments = [ - (['--memcache'], - dict(help='start/stop/restart memcache', action='store_true')), - (['--dovecot'], - dict(help='start/stop/restart dovecot', action='store_true')), - ] - - @expose(help="Start stack services") - def start(self): - """Start services""" - services = [] - if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 - or self.app.pargs.mysql or self.app.pargs.postfix - or self.app.pargs.hhvm or self.app.pargs.memcache - or self.app.pargs.dovecot or self.app.pargs.redis): - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.postfix = True - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom') or EEAptGet.is_installed(self,'nginx-mainline'): - services = services + ['nginx'] - else: - Log.info(self, "Nginx is not installed") - - if self.app.pargs.php: - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if EEAptGet.is_installed(self, 'php5-fpm'): - services = services + ['php5-fpm'] - else: - Log.info(self, "PHP5-FPM is not installed") - else: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - services = services + ['php5.6-fpm'] - else: - Log.info(self, "PHP5.6-FPM is not installed") - - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - else: - Log.info(self, "Your platform does not support PHP 7") - - if self.app.pargs.mysql: - if ((EEVariables.ee_mysql_host is "localhost") or - (EEVariables.ee_mysql_host is "127.0.0.1")): - if (EEAptGet.is_installed(self, 'mysql-server') or - EEAptGet.is_installed(self, 'percona-server-server-5.6') or - EEAptGet.is_installed(self, 'mariadb-server')): - services = services + ['mysql'] - else: - Log.info(self, "MySQL is not installed") - else: - Log.warn(self, "Remote MySQL found, " - "Unable to check MySQL service status") - - if self.app.pargs.postfix: - if EEAptGet.is_installed(self, 'postfix'): - services = services + ['postfix'] - else: - Log.info(self, "Postfix is not installed") - - if self.app.pargs.hhvm: - if EEAptGet.is_installed(self, 'hhvm'): - services = services + ['hhvm'] - else: - Log.info(self, "HHVM is not installed") - if self.app.pargs.memcache: - if EEAptGet.is_installed(self, 'memcached'): - services = services + ['memcached'] - else: - Log.info(self, "Memcache is not installed") - - if self.app.pargs.dovecot: - if EEAptGet.is_installed(self, 'dovecot-core'): - services = services + ['dovecot'] - else: - Log.info(self, "Mail server is not installed") - - if self.app.pargs.redis: - if EEAptGet.is_installed(self, 'redis-server'): - services = services + ['redis-server'] - else: - Log.info(self, "Redis server is not installed") - - for service in services: - Log.debug(self, "Starting service: {0}".format(service)) - EEService.start_service(self, service) - - @expose(help="Stop stack services") - def stop(self): - """Stop services""" - services = [] - if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 - or self.app.pargs.mysql or self.app.pargs.postfix - or self.app.pargs.hhvm or self.app.pargs.memcache - or self.app.pargs.dovecot or self.app.pargs.redis): - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.postfix = True - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom') or EEAptGet.is_installed(self,'nginx-mainline'): - services = services + ['nginx'] - else: - Log.info(self, "Nginx is not installed") - - if self.app.pargs.php: - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if EEAptGet.is_installed(self, 'php5-fpm'): - services = services + ['php5-fpm'] - else: - Log.info(self, "PHP5-FPM is not installed") - else: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - services = services + ['php5.6-fpm'] - else: - Log.info(self, "PHP5.6-FPM is not installed") - - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - else: - Log.info(self, "Your platform does not support PHP 7") - - if self.app.pargs.mysql: - if ((EEVariables.ee_mysql_host is "localhost") or - (EEVariables.ee_mysql_host is "127.0.0.1")): - if (EEAptGet.is_installed(self, 'mysql-server') or - EEAptGet.is_installed(self, 'percona-server-server-5.6') or - EEAptGet.is_installed(self, 'mariadb-server')): - services = services + ['mysql'] - else: - Log.info(self, "MySQL is not installed") - else: - Log.warn(self, "Remote MySQL found, " - "Unable to check MySQL service status") - - if self.app.pargs.postfix: - if EEAptGet.is_installed(self, 'postfix'): - services = services + ['postfix'] - else: - Log.info(self, "Postfix is not installed") - - if self.app.pargs.hhvm: - if EEAptGet.is_installed(self, 'hhvm'): - services = services + ['hhvm'] - else: - Log.info(self, "HHVM is not installed") - if self.app.pargs.memcache: - if EEAptGet.is_installed(self, 'memcached'): - services = services + ['memcached'] - else: - Log.info(self, "Memcache is not installed") - - if self.app.pargs.dovecot: - if EEAptGet.is_installed(self, 'dovecot-core'): - services = services + ['dovecot'] - else: - Log.info(self, "Mail server is not installed") - - if self.app.pargs.redis: - if EEAptGet.is_installed(self, 'redis-server'): - services = services + ['redis-server'] - else: - Log.info(self, "Redis server is not installed") - - for service in services: - Log.debug(self, "Stopping service: {0}".format(service)) - EEService.stop_service(self, service) - - @expose(help="Restart stack services") - def restart(self): - """Restart services""" - services = [] - if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 - or self.app.pargs.mysql or self.app.pargs.postfix - or self.app.pargs.hhvm or self.app.pargs.memcache - or self.app.pargs.dovecot or self.app.pargs.redis): - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.postfix = True - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom') or EEAptGet.is_installed(self,'nginx-mainline'): - services = services + ['nginx'] - else: - Log.info(self, "Nginx is not installed") - - if self.app.pargs.php: - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if EEAptGet.is_installed(self, 'php5-fpm'): - services = services + ['php5-fpm'] - else: - Log.info(self, "PHP5-FPM is not installed") - else: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - services = services + ['php5.6-fpm'] - else: - Log.info(self, "PHP5.6-FPM is not installed") - - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - else: - Log.info(self, "Your platform does not support PHP 7") - - - if self.app.pargs.mysql: - if ((EEVariables.ee_mysql_host is "localhost") or - (EEVariables.ee_mysql_host is "127.0.0.1")): - if (EEAptGet.is_installed(self, 'mysql-server') or - EEAptGet.is_installed(self, 'percona-server-server-5.6') or - EEAptGet.is_installed(self, 'mariadb-server')): - services = services + ['mysql'] - else: - Log.info(self, "MySQL is not installed") - else: - Log.warn(self, "Remote MySQL found, " - "Unable to check MySQL service status") - - if self.app.pargs.postfix: - if EEAptGet.is_installed(self, 'postfix'): - services = services + ['postfix'] - else: - Log.info(self, "Postfix is not installed") - - if self.app.pargs.hhvm: - if EEAptGet.is_installed(self, 'hhvm'): - services = services + ['hhvm'] - else: - Log.info(self, "HHVM is not installed") - if self.app.pargs.memcache: - if EEAptGet.is_installed(self, 'memcached'): - services = services + ['memcached'] - else: - Log.info(self, "Memcache is not installed") - - if self.app.pargs.dovecot: - if EEAptGet.is_installed(self, 'dovecot-core'): - services = services + ['dovecot'] - else: - Log.info(self, "Mail server is not installed") - - if self.app.pargs.redis: - if EEAptGet.is_installed(self, 'redis-server'): - services = services + ['redis-server'] - else: - Log.info(self, "Redis server is not installed") - - for service in services: - Log.debug(self, "Restarting service: {0}".format(service)) - EEService.restart_service(self, service) - - @expose(help="Get stack status") - def status(self): - """Status of services""" - services = [] - if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 - or self.app.pargs.mysql or self.app.pargs.postfix - or self.app.pargs.hhvm or self.app.pargs.memcache - or self.app.pargs.dovecot or self.app.pargs.redis): - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.postfix = True - self.app.pargs.hhvm = True - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom') or EEAptGet.is_installed(self,'nginx-mainline'): - services = services + ['nginx'] - else: - Log.info(self, "Nginx is not installed") - - if self.app.pargs.php: - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if EEAptGet.is_installed(self, 'php5-fpm'): - services = services + ['php5-fpm'] - else: - Log.info(self, "PHP5-FPM is not installed") - else: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - services = services + ['php5.6-fpm'] - else: - Log.info(self, "PHP5.6-FPM is not installed") - - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - else: - Log.info(self, "Your platform does not support PHP 7") - - if self.app.pargs.mysql: - if ((EEVariables.ee_mysql_host is "localhost") or - (EEVariables.ee_mysql_host is "127.0.0.1")): - if (EEAptGet.is_installed(self, 'mysql-server') or - EEAptGet.is_installed(self, 'percona-server-server-5.6') or - EEAptGet.is_installed(self, 'mariadb-server')): - services = services + ['mysql'] - else: - Log.info(self, "MySQL is not installed") - else: - Log.warn(self, "Remote MySQL found, " - "Unable to check MySQL service status") - - if self.app.pargs.postfix: - if EEAptGet.is_installed(self, 'postfix'): - services = services + ['postfix'] - else: - Log.info(self, "Postfix is not installed") - - if self.app.pargs.hhvm: - if EEAptGet.is_installed(self, 'hhvm'): - services = services + ['hhvm'] - else: - Log.info(self, "HHVM is not installed") - if self.app.pargs.memcache: - if EEAptGet.is_installed(self, 'memcached'): - services = services + ['memcached'] - else: - Log.info(self, "Memcache is not installed") - - if self.app.pargs.dovecot: - if EEAptGet.is_installed(self, 'dovecot-core'): - services = services + ['dovecot'] - else: - Log.info(self, "Mail server is not installed") - - if self.app.pargs.redis: - if EEAptGet.is_installed(self, 'redis-server'): - services = services + ['redis-server'] - else: - Log.info(self, "Redis server is not installed") - - for service in services: - if EEService.get_service_status(self, service): - Log.info(self, "{0:10}: {1}".format(service, "Running")) - - @expose(help="Reload stack services") - def reload(self): - """Reload service""" - services = [] - if not (self.app.pargs.nginx or self.app.pargs.php or self.app.pargs.php7 - or self.app.pargs.mysql or self.app.pargs.postfix - or self.app.pargs.hhvm or self.app.pargs.memcache - or self.app.pargs.dovecot or self.app.pargs.redis): - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.postfix = True - - if self.app.pargs.nginx: - if EEAptGet.is_installed(self, 'nginx-custom') or EEAptGet.is_installed(self,'nginx-mainline'): - services = services + ['nginx'] - else: - Log.info(self, "Nginx is not installed") - - if self.app.pargs.php: - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if EEAptGet.is_installed(self, 'php5-fpm'): - services = services + ['php5-fpm'] - else: - Log.info(self, "PHP5-FPM is not installed") - else: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - services = services + ['php5.6-fpm'] - else: - Log.info(self, "PHP5.6-FPM is not installed") - - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - - if self.app.pargs.php7: - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - if EEAptGet.is_installed(self, 'php7.0-fpm'): - services = services + ['php7.0-fpm'] - else: - Log.info(self, "PHP7.0-FPM is not installed") - else: - Log.info(self, "Your platform does not support PHP 7") - - if self.app.pargs.mysql: - if ((EEVariables.ee_mysql_host is "localhost") or - (EEVariables.ee_mysql_host is "127.0.0.1")): - if (EEAptGet.is_installed(self, 'mysql-server') or - EEAptGet.is_installed(self, 'percona-server-server-5.6') or - EEAptGet.is_installed(self, 'mariadb-server')): - services = services + ['mysql'] - else: - Log.info(self, "MySQL is not installed") - else: - Log.warn(self, "Remote MySQL found, " - "Unable to check MySQL service status") - - if self.app.pargs.postfix: - if EEAptGet.is_installed(self, 'postfix'): - services = services + ['postfix'] - else: - Log.info(self, "Postfix is not installed") - - if self.app.pargs.hhvm: - Log.info(self, "HHVM does not support to reload") - - if self.app.pargs.memcache: - if EEAptGet.is_installed(self, 'memcached'): - services = services + ['memcached'] - else: - Log.info(self, "Memcache is not installed") - - if self.app.pargs.dovecot: - if EEAptGet.is_installed(self, 'dovecot-core'): - services = services + ['dovecot'] - else: - Log.info(self, "Mail server is not installed") - - if self.app.pargs.redis: - if EEAptGet.is_installed(self, 'redis-server'): - services = services + ['redis-server'] - else: - Log.info(self, "Redis server is not installed") - - for service in services: - Log.debug(self, "Reloading service: {0}".format(service)) - EEService.reload_service(self, service) diff --git a/ee/cli/plugins/stack_upgrade.py b/ee/cli/plugins/stack_upgrade.py deleted file mode 100644 index 8d025faf8..000000000 --- a/ee/cli/plugins/stack_upgrade.py +++ /dev/null @@ -1,270 +0,0 @@ -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.logging import Log -from ee.core.variables import EEVariables -from ee.core.aptget import EEAptGet -from ee.core.apt_repo import EERepo -from ee.core.services import EEService -from ee.core.fileutils import EEFileUtils -from ee.core.shellexec import EEShellExec -from ee.core.git import EEGit -from ee.core.download import EEDownload -import configparser -import os - - -class EEStackUpgradeController(CementBaseController): - class Meta: - label = 'upgrade' - stacked_on = 'stack' - stacked_type = 'nested' - description = ('Upgrade stack safely') - arguments = [ - (['--all'], - dict(help='Upgrade all stack', action='store_true')), - (['--web'], - dict(help='Upgrade web stack', action='store_true')), - (['--admin'], - dict(help='Upgrade admin tools stack', action='store_true')), - (['--mail'], - dict(help='Upgrade mail server stack', action='store_true')), - (['--mailscanner'], - dict(help='Upgrade mail scanner stack', action='store_true')), - (['--nginx'], - dict(help='Upgrade Nginx stack', action='store_true')), - (['--nginxmainline'], - dict(help='Upgrade Nginx Mainline stack', action='store_true')), - (['--php'], - dict(help='Upgrade PHP stack', action='store_true')), - (['--mysql'], - dict(help='Upgrade MySQL stack', action='store_true')), - (['--hhvm'], - dict(help='Upgrade HHVM stack', action='store_true')), - (['--postfix'], - dict(help='Upgrade Postfix stack', action='store_true')), - (['--wpcli'], - dict(help='Upgrade WPCLI', action='store_true')), - (['--redis'], - dict(help='Upgrade Redis', action='store_true')), - (['--php56'], - dict(help="Upgrade to PHP5.6 from PHP5.5", - action='store_true')), - (['--no-prompt'], - dict(help="Upgrade Packages without any prompt", - action='store_true')), - ] - - @expose(hide=True) - def upgrade_php56(self): - if EEVariables.ee_platform_distro == "ubuntu": - if os.path.isfile("/etc/apt/sources.list.d/ondrej-php5-5_6-{0}." - "list".format(EEVariables.ee_platform_codename)): - Log.error(self, "Unable to find PHP 5.5") - else: - if not(os.path.isfile(EEVariables.ee_repo_file_path) and - EEFileUtils.grep(self, EEVariables.ee_repo_file_path, - "php55")): - Log.error(self, "Unable to find PHP 5.5") - - Log.info(self, "During PHP update process non nginx-cached" - " parts of your site may remain down.") - - # Check prompt - if (not self.app.pargs.no_prompt): - start_upgrade = input("Do you want to continue:[y/N]") - if start_upgrade != "Y" and start_upgrade != "y": - Log.error(self, "Not starting PHP package update") - - if EEVariables.ee_platform_distro == "ubuntu": - EERepo.remove(self, ppa="ppa:ondrej/php5") - EERepo.add(self, ppa=EEVariables.ee_php_repo) - else: - EEAptGet.remove(self, ["php5-xdebug"]) - EEFileUtils.searchreplace(self, EEVariables.ee_repo_file_path, - "php55", "php56") - - Log.info(self, "Updating apt-cache, please wait...") - EEAptGet.update(self) - Log.info(self, "Installing packages, please wait ...") - if (EEVariables.ee_platform_codename == 'trusty' or EEVariables.ee_platform_codename == 'xenial' or EEVariables.ee_platform_codename == 'bionic'): - EEAptGet.install(self, EEVariables.ee_php5_6 + EEVariables.ee_php_extra) - else: - EEAptGet.install(self, EEVariables.ee_php) - - if EEVariables.ee_platform_distro == "debian": - EEShellExec.cmd_exec(self, "pecl install xdebug") - - with open("/etc/php5/mods-available/xdebug.ini", - encoding='utf-8', mode='a') as myfile: - myfile.write(";zend_extension=/usr/lib/php5/20131226/" - "xdebug.so\n") - - EEFileUtils.create_symlink(self, ["/etc/php5/mods-available/" - "xdebug.ini", "/etc/php5/fpm/conf.d" - "/20-xedbug.ini"]) - - Log.info(self, "Successfully upgraded from PHP 5.5 to PHP 5.6") - - @expose(hide=True) - def default(self): - # All package update - if ((not self.app.pargs.php56)): - - apt_packages = [] - packages = [] - - if ((not self.app.pargs.web) and (not self.app.pargs.nginx) and - (not self.app.pargs.php) and (not self.app.pargs.mysql) and - (not self.app.pargs.postfix) and (not self.app.pargs.hhvm) and - (not self.app.pargs.mailscanner) and (not self.app.pargs.all) - and (not self.app.pargs.wpcli) and (not self.app.pargs.redis) and (not self.app.pargs.nginxmainline)): - self.app.pargs.web = True - - if self.app.pargs.all: - self.app.pargs.web = True - self.app.pargs.mail = True - - if self.app.pargs.web: - if EEAptGet.is_installed(self, 'nginx-custom'): - self.app.pargs.nginx = True - else: - Log.info(self, "Nginx is not already installed") - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.postfix = True - self.app.pargs.wpcli = True - - if self.app.pargs.mail: - self.app.pargs.nginx = True - self.app.pargs.php = True - self.app.pargs.mysql = True - self.app.pargs.wpcli = True - self.app.pargs.postfix = True - - if EEAptGet.is_installed(self, 'dovecot-core'): - apt_packages = apt_packages + EEVariables.ee_mail - self.app.pargs.mailscanner = True - else: - Log.info(self, "Mail server is not installed") - - if self.app.pargs.nginx : - if EEAptGet.is_installed(self, 'nginx-custom'): - apt_packages = apt_packages + EEVariables.ee_nginx - else: - Log.info(self, "Nginx Stable is not already installed") - - if self.app.pargs.php: - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if EEAptGet.is_installed(self, 'php5-fpm'): - apt_packages = apt_packages + EEVariables.ee_php - else: - Log.info(self, "PHP is not installed") - if EEAptGet.is_installed(self, 'php7.0-fpm'): - apt_packages = apt_packages + EEVariables.ee_php7_0 - else: - if EEAptGet.is_installed(self, 'php5.6-fpm'): - apt_packages = apt_packages + EEVariables.ee_php5_6 + EEVariables.ee_php_extra - else: - Log.info(self, "PHP 5.6 is not installed") - if EEAptGet.is_installed(self, 'php7.0-fpm'): - apt_packages = apt_packages + EEVariables.ee_php7_0 + EEVariables.ee_php_extra - else: - Log.info(self, "PHP 7.0 is not installed") - - if self.app.pargs.hhvm: - if EEAptGet.is_installed(self, 'hhvm'): - apt_packages = apt_packages + EEVariables.ee_hhvm - else: - Log.info(self, "HHVM is not installed") - - if self.app.pargs.mysql: - if EEAptGet.is_installed(self, 'mariadb-server'): - apt_packages = apt_packages + EEVariables.ee_mysql - else: - Log.info(self, "MariaDB is not installed") - - if self.app.pargs.postfix: - if EEAptGet.is_installed(self, 'postfix'): - apt_packages = apt_packages + EEVariables.ee_postfix - else: - Log.info(self, "Postfix is not installed") - - if self.app.pargs.redis: - if EEAptGet.is_installed(self, 'redis-server'): - apt_packages = apt_packages + EEVariables.ee_redis - else: - Log.info(self, "Redis is not installed") - - if self.app.pargs.wpcli: - if os.path.isfile('/usr/bin/wp'): - packages = packages + [["https://github.com/wp-cli/wp-cli/" - "releases/download/v{0}/" - "wp-cli-{0}.phar" - "".format(EEVariables.ee_wp_cli), - "/usr/bin/wp", - "WP-CLI"]] - else: - Log.info(self, "WPCLI is not installed with EasyEngine") - - if self.app.pargs.mailscanner: - if EEAptGet.is_installed(self, 'amavisd-new'): - apt_packages = (apt_packages + EEVariables.ee_mailscanner) - else: - Log.info(self, "MailScanner is not installed") - - if len(packages) or len(apt_packages): - - Log.info(self, "During package update process non nginx-cached" - " parts of your site may remain down") - # Check prompt - if (not self.app.pargs.no_prompt): - start_upgrade = input("Do you want to continue:[y/N]") - if start_upgrade != "Y" and start_upgrade != "y": - Log.error(self, "Not starting package update") - - Log.info(self, "Updating packages, please wait...") - if len(apt_packages): - # apt-get update - EEAptGet.update(self) - # Update packages - EEAptGet.install(self, apt_packages) - - # Post Actions after package updates - if (set(EEVariables.ee_nginx).issubset(set(apt_packages))): - EEService.restart_service(self, 'nginx') - if (EEVariables.ee_platform_distro == 'debian' or EEVariables.ee_platform_codename == 'precise'): - if set(EEVariables.ee_php).issubset(set(apt_packages)): - EEService.restart_service(self, 'php5-fpm') - else: - if set(EEVariables.ee_php5_6).issubset(set(apt_packages)): - EEService.restart_service(self, 'php5.6-fpm') - if set(EEVariables.ee_php7_0).issubset(set(apt_packages)): - EEService.restart_service(self, 'php7.0-fpm') - if set(EEVariables.ee_hhvm).issubset(set(apt_packages)): - EEService.restart_service(self, 'hhvm') - if set(EEVariables.ee_postfix).issubset(set(apt_packages)): - EEService.restart_service(self, 'postfix') - if set(EEVariables.ee_mysql).issubset(set(apt_packages)): - EEService.restart_service(self, 'mysql') - if set(EEVariables.ee_mail).issubset(set(apt_packages)): - EEService.restart_service(self, 'dovecot') - if set(EEVariables.ee_redis).issubset(set(apt_packages)): - EEService.restart_service(self, 'redis-server') - - if len(packages): - if self.app.pargs.wpcli: - EEFileUtils.remove(self,['/usr/bin/wp']) - - Log.debug(self, "Downloading following: {0}".format(packages)) - EEDownload.download(self, packages) - - if self.app.pargs.wpcli: - EEFileUtils.chmod(self, "/usr/bin/wp", 0o775) - - Log.info(self, "Successfully updated packages") - - # PHP 5.6 to 5.6 - elif (self.app.pargs.php56): - self.upgrade_php56() - else: - self.app.args.print_help() diff --git a/ee/cli/plugins/sync.py b/ee/cli/plugins/sync.py deleted file mode 100644 index dd89dfed6..000000000 --- a/ee/cli/plugins/sync.py +++ /dev/null @@ -1,95 +0,0 @@ -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.fileutils import EEFileUtils -from ee.cli.plugins.sitedb import * -from ee.core.mysql import * -from ee.core.logging import Log - - -def ee_sync_hook(app): - # do something with the ``app`` object here. - pass - - -class EESyncController(CementBaseController): - class Meta: - label = 'sync' - stacked_on = 'base' - stacked_type = 'nested' - description = 'synchronize EasyEngine database' - - @expose(hide=True) - def default(self): - self.sync() - - @expose(hide=True) - def sync(self): - """ - 1. reads database information from wp/ee-config.php - 2. updates records into ee database accordingly. - """ - Log.info(self, "Synchronizing ee database, please wait...") - sites = getAllsites(self) - if not sites: - pass - for site in sites: - if site.site_type in ['mysql', 'wp', 'wpsubdir', 'wpsubdomain']: - ee_site_webroot = site.site_path - # Read config files - configfiles = glob.glob(ee_site_webroot + '/*-config.php') - - #search for wp-config.php inside htdocs/ - if not configfiles: - Log.debug(self, "Config files not found in {0}/ " - .format(ee_site_webroot)) - if site.site_type != 'mysql': - Log.debug(self, "Searching wp-config.php in {0}/htdocs/ " - .format(ee_site_webroot)) - configfiles = glob.glob(ee_site_webroot + '/htdocs/wp-config.php') - - if configfiles: - if EEFileUtils.isexist(self, configfiles[0]): - ee_db_name = (EEFileUtils.grep(self, configfiles[0], - 'DB_NAME').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - ee_db_user = (EEFileUtils.grep(self, configfiles[0], - 'DB_USER').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - ee_db_pass = (EEFileUtils.grep(self, configfiles[0], - 'DB_PASSWORD').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - ee_db_host = (EEFileUtils.grep(self, configfiles[0], - 'DB_HOST').split(',')[1] - .split(')')[0].strip().replace('\'', '')) - - # Check if database really exist - try: - if not EEMysql.check_db_exists(self, ee_db_name): - # Mark it as deleted if not exist - ee_db_name = 'deleted' - ee_db_user = 'deleted' - ee_db_pass = 'deleted' - except StatementExcecutionError as e: - Log.debug(self, str(e)) - except Exception as e: - Log.debug(self, str(e)) - - if site.db_name != ee_db_name: - # update records if any mismatch found - Log.debug(self, "Updating ee db record for {0}" - .format(site.sitename)) - updateSiteInfo(self, site.sitename, - db_name=ee_db_name, - db_user=ee_db_user, - db_password=ee_db_pass, - db_host=ee_db_host) - else: - Log.debug(self, "Config files not found for {0} " - .format(site.sitename)) - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EESyncController) - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_sync_hook) diff --git a/ee/cli/plugins/update.py b/ee/cli/plugins/update.py deleted file mode 100644 index 8680d68ef..000000000 --- a/ee/cli/plugins/update.py +++ /dev/null @@ -1,45 +0,0 @@ -from cement.core.controller import CementBaseController, expose -from cement.core import handler, hook -from ee.core.download import EEDownload -from ee.core.logging import Log -import time -import os - - -def ee_update_hook(app): - # do something with the ``app`` object here. - pass - - -class EEUpdateController(CementBaseController): - class Meta: - label = 'ee_update' - stacked_on = 'base' - aliases = ['update'] - aliases_only = True - stacked_type = 'nested' - description = ('update EasyEngine to latest version') - usage = "ee update" - - @expose(hide=True) - def default(self): - filename = "eeupdate" + time.strftime("%Y%m%d-%H%M%S") - EEDownload.download(self, [["http://rt.cx/eeup", - "/tmp/{0}".format(filename), - "update script"]]) - try: - Log.info(self, "updating EasyEngine, please wait...") - os.system("bash /tmp/{0}".format(filename)) - except OSError as e: - Log.debug(self, str(e)) - Log.error(self, "EasyEngine update failed !") - except Exception as e: - Log.debug(self, str(e)) - Log.error(self, "EasyEngine update failed !") - - -def load(app): - # register the plugin class.. this only happens if the plugin is enabled - handler.register(EEUpdateController) - # register a hook (function) to run after arguments are parsed. - hook.register('post_argument_parsing', ee_update_hook) diff --git a/ee/cli/templates/15-content_filter_mode.mustache b/ee/cli/templates/15-content_filter_mode.mustache deleted file mode 100644 index 6fd8f218e..000000000 --- a/ee/cli/templates/15-content_filter_mode.mustache +++ /dev/null @@ -1,27 +0,0 @@ -use strict; - -# You can modify this file to re-enable SPAM checking through spamassassin -# and to re-enable antivirus checking. - -# -# Default antivirus checking mode -# Please note, that anti-virus checking is DISABLED by -# default. -# If You wish to enable it, please uncomment the following lines: - - -@bypass_virus_checks_maps = ( - \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re); - - -# -# Default SPAM checking mode -# Please note, that anti-spam checking is DISABLED by -# default. -# If You wish to enable it, please uncomment the following lines: - - -@bypass_spam_checks_maps = ( - \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re); - -1; # ensure a defined return diff --git a/ee/cli/templates/22222.mustache b/ee/cli/templates/22222.mustache deleted file mode 100644 index 21dbcd1a3..000000000 --- a/ee/cli/templates/22222.mustache +++ /dev/null @@ -1,64 +0,0 @@ -# EasyEngine admin NGINX CONFIGURATION - -server { - - listen 22222 default_server ssl http2; - - access_log /var/log/nginx/22222.access.log rt_cache; - error_log /var/log/nginx/22222.error.log; - - ssl_certificate {{webroot}}22222/cert/22222.crt; - ssl_certificate_key {{webroot}}22222/cert/22222.key; - - # Force HTTP to HTTPS - error_page 497 =200 https://$host:22222$request_uri; - - root {{webroot}}22222/htdocs; - index index.php index.htm index.html; - - # Turn on directory listing - autoindex on; - - # HTTP Authentication on port 22222 - include common/acl.conf; - - location / { - try_files $uri $uri/ /index.php?$args; - } - - # Display menu at location /fpm/status/ - location = /fpm/status/ {} - - location ~ /fpm/status/(.*) { - try_files $uri =404; - include fastcgi_params; - fastcgi_param SCRIPT_NAME /status; - fastcgi_pass $1; - } - - location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php; - } - - # ViMbAdmin Rules - location = /vimbadmin/ { - return 301 $scheme://$host:22222/vimbadmin/public/; - } - - location ~* \.(js|css|jpg|gif|png)$ { - root {{webroot}}22222/htdocs/; - } - - location ~* /vimbadmin/public/(.*)/(.*) { - root {{webroot}}22222/htdocs/vimbadmin/public; - try_files $uri $uri/ /vimbadmin/public/index.php?$args; - } - - location ~* /vimbadmin/public/(.*) { - root {{webroot}}22222/htdocs/vimbadmin/public; - try_files $uri $uri/ /vimbadmin/public/index.php?$args; - } - -} diff --git a/ee/cli/templates/50-user.mustache b/ee/cli/templates/50-user.mustache deleted file mode 100644 index 0cee70fbd..000000000 --- a/ee/cli/templates/50-user.mustache +++ /dev/null @@ -1,17 +0,0 @@ -use strict; -$sa_spam_subject_tag = undef; -$spam_quarantine_to = undef; -$sa_tag_level_deflt = undef; - -# Prevent spams from automatically rejected by mail-server -$final_spam_destiny = D_PASS; - -# We need to provide list of domains for which filtering need to be done -@lookup_sql_dsn = ( -['DBI:mysql:database=vimbadmin;host={{host}};port=3306', -'vimbadmin', -'{{password}}']); - -$sql_select_policy = 'SELECT domain FROM domain WHERE CONCAT("@",domain) IN (%k)'; - -1; # ensure a defined return diff --git a/ee/cli/templates/acl.mustache b/ee/cli/templates/acl.mustache deleted file mode 100644 index 122675f9c..000000000 --- a/ee/cli/templates/acl.mustache +++ /dev/null @@ -1,8 +0,0 @@ -# EasyEngine (ee) protect locations using -# HTTP authentication || IP address -satisfy any; -auth_basic "Restricted Area"; -auth_basic_user_file htpasswd-ee; -# Allowed IP Address List -allow 127.0.0.1; -deny all; diff --git a/ee/cli/templates/anemometer.mustache b/ee/cli/templates/anemometer.mustache deleted file mode 100644 index 7b85a6a7d..000000000 --- a/ee/cli/templates/anemometer.mustache +++ /dev/null @@ -1,255 +0,0 @@ - '{{host}}', - 'port' => '{{port}}', - 'db' => 'slow_query_log', - 'user' => '{{user}}', - 'password' => '{{password}}', - 'tables' => array( - 'global_query_review' => 'fact', - 'global_query_review_history' => 'dimension' - ), - 'source_type' => 'slow_query_log' -); - -$conf['default_report_action'] = 'report'; - -$conf['reviewers'] = array( 'dba1','dba2'); -$conf['review_types'] = array( 'good', 'bad', 'ticket-created', 'needs-fix', 'fixed', 'needs-analysis', 'review-again'); - -$conf['history_defaults'] = array( - 'output' => 'table', - 'fact-group' => 'date', - 'fact-order' => 'date DESC', - 'fact-limit' => '90', - 'dimension-ts_min_start' => date("Y-m-d H:i:s", strtotime( '-90 day')), - 'dimension-ts_min_end' => date("Y-m-d H:i:s"), - 'table_fields' => array('date', 'index_ratio','query_time_avg','rows_sent_avg','ts_cnt','Query_time_sum','Lock_time_sum','Rows_sent_sum','Rows_examined_sum','Tmp_table_sum','Filesort_sum','Full_scan_sum') -); - -$conf['report_defaults'] = array( - 'fact-group' => 'checksum', - 'fact-order' => 'Query_time_sum DESC', - 'fact-limit' => '20', - 'dimension-ts_min_start' => date("Y-m-d H:i:s", strtotime( '-1 day')), - 'dimension-ts_min_end' => date("Y-m-d H:i:s"), - 'table_fields' => array('checksum','snippet', 'index_ratio','query_time_avg','rows_sent_avg','ts_cnt','Query_time_sum','Lock_time_sum','Rows_sent_sum','Rows_examined_sum','Tmp_table_sum','Filesort_sum','Full_scan_sum'), - 'dimension-pivot-hostname_max' => null -); - -$conf['graph_defaults'] = array( - 'fact-group' => 'minute_ts', - 'fact-order' => 'minute_ts', - 'fact-limit' => '', - 'dimension-ts_min_start' => date("Y-m-d H:i:s", strtotime( '-7 day')), - 'dimension-ts_min_end' => date("Y-m-d H:i:s"), - 'table_fields' => array('minute_ts'), - 'plot_field' => 'Query_time_sum', -); - -$conf['report_defaults']['performance_schema'] = array( - 'fact-order' => 'SUM_TIMER_WAIT DESC', - 'fact-limit' => '20', - 'fact-group' => 'DIGEST', - 'table_fields' => array( 'DIGEST', 'snippet', 'index_ratio', 'COUNT_STAR', 'SUM_TIMER_WAIT', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) -); - -$conf['history_defaults']['performance_schema'] = array( - 'fact-order' => 'SUM_TIMER_WAIT DESC', - 'fact-limit' => '20', - 'fact-group' => 'DIGEST', - 'table_fields' => array( 'DIGEST', 'index_ratio', 'COUNT_STAR', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) -); - -$conf['report_defaults']['performance_schema_history'] = array( - 'fact-group' => 'DIGEST', - 'fact-order' => 'SUM_TIMER_WAIT DESC', - 'fact-limit' => '20', - 'dimension-FIRST_SEEN_start' => date("Y-m-d H:i:s", strtotime( '-1 day')), - 'dimension-FIRST_SEEN_end' => date("Y-m-d H:i:s"), - 'table_fields' => array( 'DIGEST', 'snippet', 'index_ratio', 'COUNT_STAR', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) -); - -$conf['graph_defaults']['performance_schema_history'] = array( - 'fact-group' => 'minute_ts', - 'fact-order' => 'minute_ts', - 'fact-limit' => '', - 'dimension-FIRST_SEEN_start' => date("Y-m-d H:i:s", strtotime( '-7 day')), - 'dimension-FIRST_SEEN_end' => date("Y-m-d H:i:s"), - 'table_fields' => array('minute_ts'), - 'plot_field' => 'SUM_TIMER_WAIT', - 'dimension-pivot-hostname_max' => null -); - -$conf['history_defaults']['performance_schema_history'] = array( - 'output' => 'table', - 'fact-group' => 'date', - 'fact-order' => 'date DESC', - 'fact-limit' => '90', - 'dimension-FIRST_SEEN_start' => date("Y-m-d H:i:s", strtotime( '-90 day')), - 'dimension-FIRST_SEEN_end' => date("Y-m-d H:i:s"), - 'table_fields' => array( 'date', 'snippet', 'index_ratio', 'COUNT_STAR', 'SUM_LOCK_TIME','SUM_ROWS_AFFECTED','SUM_ROWS_SENT','SUM_ROWS_EXAMINED','SUM_CREATED_TMP_TABLES','SUM_SORT_SCAN','SUM_NO_INDEX_USED' ) -); -$conf['plugins'] = array( - - 'visual_explain' => '/usr/bin/pt-visual-explain', - 'query_advisor' => '/usr/bin/pt-query-advisor', - - 'show_create' => true, - 'show_status' => true, - - 'explain' => function ($sample) { - $conn = array(); - - if (!array_key_exists('hostname_max',$sample) or strlen($sample['hostname_max']) < 5) - { - return; - } - - $pos = strpos($sample['hostname_max'], ':'); - if ($pos === false) - { - $conn['port'] = 3306; - $conn['host'] = $sample['hostname_max']; - } - else - { - $parts = preg_split("/:/", $sample['hostname_max']); - $conn['host'] = $parts[0]; - $conn['port'] = $parts[1]; - } - - $conn['db'] = 'mysql'; - if ($sample['db_max'] != '') - { - $conn['db'] = $sample['db_max']; - } - - $conn['user'] = '{{user}}'; - $conn['password'] = '{{password}}'; - - return $conn; - }, -); - -$conf['reports']['slow_query_log'] = array( - 'join' => array ( - 'dimension' => 'USING (`checksum`)' - ), - 'fields' => array( - 'fact' => array( - 'group' => 'group', - 'order' => 'order', - 'having' => 'having', - 'limit' => 'limit', - 'first_seen'=> 'clear|reldate|ge|where', - 'where' => 'raw_where', - 'sample' => 'clear|like|where', - 'checksum' => 'clear|where', - 'reviewed_status' => 'clear|where', - - ), - - 'dimension' => array( - 'extra_fields' => 'where', - 'hostname_max' => 'clear|where', - 'ts_min' => 'date_range|reldate|clear|where', - 'pivot-hostname_max' => 'clear|pivot|select', - 'pivot-checksum' => 'clear|pivot|select', - ), - ), - 'custom_fields' => array( - 'checksum' => 'checksum', - 'date' => 'DATE(ts_min)', - 'hour' => 'substring(ts_min,1,13)', - 'hour_ts' => 'round(unix_timestamp(substring(ts_min,1,13)))', - 'minute_ts' => 'round(unix_timestamp(substring(ts_min,1,16)))', - 'minute' => 'substring(ts_min,1,16)', - 'snippet' => 'LEFT(dimension.sample,20)', - 'index_ratio' =>'ROUND(SUM(Rows_examined_sum)/SUM(rows_sent_sum),2)', - 'query_time_avg' => 'SUM(Query_time_sum) / SUM(ts_cnt)', - 'rows_sent_avg' => 'ROUND(SUM(Rows_sent_sum)/SUM(ts_cnt),0)', - ), - - 'callbacks' => array( - 'table' => array( - 'date' => function ($x) { $type=''; if ( date('N',strtotime($x)) >= 6) { $type = 'weekend'; } return array($x,$type); }, - 'checksum' => function ($x) { return array(dec2hex($x), ''); } - ) - ) - -); - -$conf['reports']['performance_schema'] = array( - 'fields' => array( - 'fact' => array( - 'order' => 'order', - 'having' => 'having', - 'limit' => 'limit', - 'first_seen' => 'date_range|reldate|clear|where', - 'where' => 'raw_where', - 'DIGEST' => 'clear|where', - 'DIGEST_TEXT' => 'clear|like|where', - 'group' => 'group', - ), - ), - 'custom_fields' => array( - 'snippet' => 'LEFT(fact.DIGEST_TEXT,20)', - 'index_ratio' =>'ROUND(SUM_ROWS_EXAMINED/SUM_ROWS_SENT,2)', - 'rows_sent_avg' => 'ROUND(SUM_ROWS_SENT/COUNT_STAR,0)', - - ), - - 'special_field_names' => array( - 'time' => 'FIRST_SEEN', - 'checksum' => 'DIGEST', - 'sample' => 'DIGEST_TEXT', - 'fingerprint' => 'DIGEST_TEXT', - ), -); - -$conf['reports']['performance_schema_history'] = array( - 'join' => array ( - 'dimension' => 'USING (`DIGEST`)' - ), - 'fields' => array( - 'fact' => array( - 'group' => 'group', - 'order' => 'order', - 'having' => 'having', - 'limit' => 'limit', - 'first_seen'=> 'clear|reldate|ge|where', - 'where' => 'raw_where', - 'DIGEST_TEXT' => 'clear|like|where', - 'DIGEST' => 'clear|where', - 'reviewed_status' => 'clear|where', - - ), - - 'dimension' => array( - 'extra_fields' => 'where', - 'hostname' => 'clear|where', - 'FIRST_SEEN' => 'date_range|reldate|clear|where', - 'pivot-hostname' => 'clear|pivot|select', - ), - ), - 'custom_fields' => array( - 'date' => 'DATE(fact.FIRST_SEEN)', - 'snippet' => 'LEFT(fact.DIGEST_TEXT,20)', - 'index_ratio' =>'ROUND(SUM_ROWS_EXAMINED/SUM_ROWS_SENT,2)', - 'rows_sent_avg' => 'ROUND(SUM_ROWS_SENT/COUNT_STAR,0)', - 'hour' => 'substring(dimension.FIRST_SEEN,1,13)', - 'hour_ts' => 'round(unix_timestamp(substring(dimension.FIRST_SEEN,1,13)))', - 'minute_ts' => 'round(unix_timestamp(substring(dimension.FIRST_SEEN,1,16)))', - 'minute' => 'substring(dimension.FIRST_SEEN,1,16)', - ), - - 'special_field_names' => array( - 'time' => 'FIRST_SEEN', - 'checksum' => 'DIGEST', - 'hostname' => 'hostname', - 'sample' => 'DIGEST_TEXT' - ), -); - -?> diff --git a/ee/cli/templates/auth-sql-conf.mustache b/ee/cli/templates/auth-sql-conf.mustache deleted file mode 100644 index 8854747ba..000000000 --- a/ee/cli/templates/auth-sql-conf.mustache +++ /dev/null @@ -1,11 +0,0 @@ -passdb { - driver = sql - args = /etc/dovecot/dovecot-sql.conf.ext -} -userdb { - driver = prefetch -} -userdb { - driver = sql - args = /etc/dovecot/dovecot-sql.conf.ext -} diff --git a/ee/cli/templates/blockips.mustache b/ee/cli/templates/blockips.mustache deleted file mode 100644 index 8228bedbb..000000000 --- a/ee/cli/templates/blockips.mustache +++ /dev/null @@ -1,2 +0,0 @@ -# Block IP Address -# deny 1.1.1.1; diff --git a/ee/cli/templates/default-sieve.mustache b/ee/cli/templates/default-sieve.mustache deleted file mode 100644 index 625323223..000000000 --- a/ee/cli/templates/default-sieve.mustache +++ /dev/null @@ -1,4 +0,0 @@ -require "fileinto"; -if header :contains "X-Spam-Flag" "YES" { - fileinto "Junk"; -} diff --git a/ee/cli/templates/dovecot-sql-conf.mustache b/ee/cli/templates/dovecot-sql-conf.mustache deleted file mode 100644 index 0ec50c113..000000000 --- a/ee/cli/templates/dovecot-sql-conf.mustache +++ /dev/null @@ -1,12 +0,0 @@ -driver = mysql -connect = host={{host}} user=vimbadmin password={{password}} dbname=vimbadmin -default_pass_scheme = MD5 -password_query = SELECT username as user, password as password, \ -homedir AS home, maildir AS mail, \ -concat('*:bytes=', quota) as quota_rule, uid, gid \ -FROM mailbox \ -WHERE username = '%Lu' AND active = '1' \ -AND ( access_restriction = 'ALL' OR LOCATE( access_restriction, '%Us' ) > 0 ) -user_query = SELECT homedir AS home, maildir AS mail, \ -concat('*:bytes=', quota) as quota_rule, uid, gid \ -FROM mailbox WHERE username = '%u' diff --git a/ee/cli/templates/dovecot.mustache b/ee/cli/templates/dovecot.mustache deleted file mode 100644 index f7370da83..000000000 --- a/ee/cli/templates/dovecot.mustache +++ /dev/null @@ -1,60 +0,0 @@ -protocols = imap pop3 lmtp sieve - -mail_location = maildir:/var/vmail/%d/%n - -disable_plaintext_auth = no -auth_mechanisms = plain login -#!include auth-system.conf.ext -!include auth-sql.conf.ext - -ssl_protocols = !SSLv2 !SSLv3 - -service lmtp { - unix_listener /var/spool/postfix/private/dovecot-lmtp { - mode = 0600 - user = postfix - group = postfix - } -} -service auth { - unix_listener /var/spool/postfix/private/auth { - mode = 0666 - user = postfix - group = postfix - } - unix_listener auth-userdb { - mode = 0600 - user = vmail - } - user = dovecot -} -service auth-worker { - user = vmail -} - -log_path = /var/log/dovecot.log - -mail_plugins = $mail_plugins autocreate - -plugin { - autocreate = Trash - autocreate2 = Junk - autocreate3 = Drafts - autocreate4 = Sent - autosubscribe = Trash - autosubscribe2 = Junk - autosubscribe3 = Drafts - autosubscribe4 = Sent -} - -protocol lmtp { - postmaster_address = {{email}} - mail_plugins = $mail_plugins sieve -} - -plugin { - sieve = ~/.dovecot.sieve - sieve_global_path = /var/lib/dovecot/sieve/default.sieve - sieve_global_dir = /var/lib/dovecot/sieve/ - sieve_dir = ~/sieve -} diff --git a/ee/cli/templates/ee-plus.mustache b/ee/cli/templates/ee-plus.mustache deleted file mode 100644 index fc90602bf..000000000 --- a/ee/cli/templates/ee-plus.mustache +++ /dev/null @@ -1,84 +0,0 @@ -## -# EasyEngine Settings -## - - -tcp_nopush on; -tcp_nodelay on; -types_hash_max_size 2048; - -server_tokens off; -reset_timedout_connection on; -add_header X-Powered-By "EasyEngine {{ version }}"; -add_header rt-Fastcgi-Cache $upstream_cache_status; - -# Limit Request -limit_req_status 403; -limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; - -# Proxy Settings -# set_real_ip_from proxy-server-ip; -# real_ip_header X-Forwarded-For; - -fastcgi_read_timeout 300; -client_max_body_size 100m; - -## -# SSL Settings -## - -ssl_session_cache shared:SSL:20m; -ssl_session_timeout 10m; -ssl_prefer_server_ciphers on; -ssl_ciphers HIGH:!aNULL:!MD5:!kEDH; -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - -## -# Basic Settings -## -server_names_hash_bucket_size 16384; -# server_name_in_redirect off; - - -## -# Logging Settings -## - -access_log /var/log/nginx/access.log; -error_log /var/log/nginx/error.log; - -# Log format Settings -log_format rt_cache '$remote_addr $upstream_response_time $upstream_cache_status [$time_local] ' -'$http_host "$request" $status $body_bytes_sent ' -'"$http_referer" "$http_user_agent" "$request_body"'; - -## -# Gzip Settings -## - -gzip on; -gzip_disable "msie6"; - -gzip_vary on; -gzip_proxied any; -gzip_comp_level 6; -gzip_buffers 16 8k; -gzip_http_version 1.1; -gzip_types - application/atom+xml - application/javascript - application/json - application/rss+xml - application/vnd.ms-fontobject - application/x-font-ttf - application/x-web-app-manifest+json - application/xhtml+xml - application/xml - font/opentype - image/svg+xml - image/x-icon - text/css - text/plain - text/x-component - text/xml - text/javascript; diff --git a/ee/cli/templates/fastcgi.mustache b/ee/cli/templates/fastcgi.mustache deleted file mode 100644 index 6d4bc4baa..000000000 --- a/ee/cli/templates/fastcgi.mustache +++ /dev/null @@ -1,10 +0,0 @@ -# FastCGI cache settings -fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:50m inactive=60m; -fastcgi_cache_key "$scheme$request_method$host$request_uri"; -fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_503; -fastcgi_cache_valid 200 301 302 404 1h; -fastcgi_buffers 16 16k; -fastcgi_buffer_size 32k; -fastcgi_param SERVER_NAME $http_host; -fastcgi_ignore_headers Cache-Control Expires Set-Cookie; -fastcgi_keep_conn on; diff --git a/ee/cli/templates/info_mysql.mustache b/ee/cli/templates/info_mysql.mustache deleted file mode 100644 index 8d7ac950e..000000000 --- a/ee/cli/templates/info_mysql.mustache +++ /dev/null @@ -1,10 +0,0 @@ - -MySQL ({{version}}) on {{host}}: - -port {{port}} -wait_timeout {{wait_timeout}} -interactive_timeout {{interactive_timeout}} -max_used_connections {{max_used_connections}} -datadir {{datadir}} -socket {{socket}} -my.cnf [PATH] /etc/mysql/conf.d/my.cnf diff --git a/ee/cli/templates/info_nginx.mustache b/ee/cli/templates/info_nginx.mustache deleted file mode 100644 index 6420405bf..000000000 --- a/ee/cli/templates/info_nginx.mustache +++ /dev/null @@ -1,10 +0,0 @@ - -NGINX ({{version}}): - -user {{user}} -worker_processes {{worker_processes}} -worker_connections {{worker_connections}} -keepalive_timeout {{keepalive_timeout}} -fastcgi_read_timeout {{fastcgi_read_timeout}} -client_max_body_size {{client_max_body_size}} -allow {{allow}} diff --git a/ee/cli/templates/info_php.mustache b/ee/cli/templates/info_php.mustache deleted file mode 100644 index 1638cf8a5..000000000 --- a/ee/cli/templates/info_php.mustache +++ /dev/null @@ -1,35 +0,0 @@ - -PHP ({{version}}): - -user {{user}} -expose_php {{expose_php}} -memory_limit {{memory_limit}} -post_max_size {{post_max_size}} -upload_max_filesize {{upload_max_filesize}} -max_execution_time {{max_execution_time}} - -Information about www.conf -ping.path {{www_ping_path}} -pm.status_path {{www_pm_status_path}} -process_manager {{www_pm}} -pm.max_requests {{www_pm_max_requests}} -pm.max_children {{www_pm_max_children}} -pm.start_servers {{www_pm_start_servers}} -pm.min_spare_servers {{www_pm_min_spare_servers}} -pm.max_spare_servers {{www_pm_max_spare_servers}} -request_terminate_timeout {{www_request_terminate_timeout}} -xdebug.profiler_enable_trigger {{www_xdebug_profiler_enable_trigger}} -listen {{www_listen}} - -Information about debug.conf -ping.path {{debug_ping_path}} -pm.status_path {{debug_pm_status_path}} -process_manager {{debug_pm}} -pm.max_requests {{debug_pm_max_requests}} -pm.max_children {{debug_pm_max_children}} -pm.start_servers {{debug_pm_start_servers}} -pm.min_spare_servers {{debug_pm_min_spare_servers}} -pm.max_spare_servers {{debug_pm_max_spare_servers}} -request_terminate_timeout {{debug_request_terminate_timeout}} -xdebug.profiler_enable_trigger {{debug_xdebug_profiler_enable_trigger}} -listen {{debug_listen}} diff --git a/ee/cli/templates/locations-php7.mustache b/ee/cli/templates/locations-php7.mustache deleted file mode 100644 index ee0ebb484..000000000 --- a/ee/cli/templates/locations-php7.mustache +++ /dev/null @@ -1,68 +0,0 @@ -# NGINX CONFIGURATION FOR COMMON LOCATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -# Basic locations files -location = /favicon.ico { - access_log off; - log_not_found off; - expires max; -} -location = /robots.txt { - # Some WordPress plugin gererate robots.txt file - # Refer #340 issue - try_files $uri $uri/ /index.php?$args; - access_log off; - log_not_found off; -} -# Cache static files -location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|swf)$ { - add_header "Access-Control-Allow-Origin" "*"; - access_log off; - log_not_found off; - expires max; -} -# Security settings for better privacy -# Deny hidden files -location ~ /\.well-known { - allow all; -} -location ~ /\. { - deny all; - access_log off; - log_not_found off; -} -# Deny backup extensions & log files -location ~* ^.+\.(bak|log|old|orig|original|php#|php~|php_bak|save|swo|swp|sql)$ { - deny all; - access_log off; - log_not_found off; -} -# Return 403 forbidden for readme.(txt|html) or license.(txt|html) or example.(txt|html) -if ($uri ~* "^.+(readme|license|example)\.(txt|html)$") { - return 403; -} -# Status pages -location = /nginx_status { - stub_status on; - access_log off; - include common/acl.conf; -} -location ~ ^/(status|ping)$ { - include fastcgi_params; - fastcgi_pass php7; - include common/acl.conf; -} -# EasyEngine (ee) utilities -# phpMyAdmin settings -location = /pma { - return 301 https://$host:22222/db/pma; -} -location = /phpMyAdmin { - return 301 https://$host:22222/db/pma; -} -location = /phpmyadmin { - return 301 https://$host:22222/db/pma; -} -# Adminer settings -location = /adminer { - return 301 https://$host:22222/db/adminer; -} diff --git a/ee/cli/templates/locations.mustache b/ee/cli/templates/locations.mustache deleted file mode 100644 index 3bfda44f4..000000000 --- a/ee/cli/templates/locations.mustache +++ /dev/null @@ -1,68 +0,0 @@ -# NGINX CONFIGURATION FOR COMMON LOCATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -# Basic locations files -location = /favicon.ico { - access_log off; - log_not_found off; - expires max; -} -location = /robots.txt { - # Some WordPress plugin gererate robots.txt file - # Refer #340 issue - try_files $uri $uri/ /index.php?$args; - access_log off; - log_not_found off; -} -# Cache static files -location ~* \.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|swf)$ { - add_header "Access-Control-Allow-Origin" "*"; - access_log off; - log_not_found off; - expires max; -} -# Security settings for better privacy -# Deny hidden files -location ~ /\.well-known { - allow all; -} -location ~ /\. { - deny all; - access_log off; - log_not_found off; -} -# Deny backup extensions & log files -location ~* ^.+\.(bak|log|old|orig|original|php#|php~|php_bak|save|swo|swp|sql)$ { - deny all; - access_log off; - log_not_found off; -} -# Return 403 forbidden for readme.(txt|html) or license.(txt|html) or example.(txt|html) -if ($uri ~* "^.+(readme|license|example)\.(txt|html)$") { - return 403; -} -# Status pages -location = /nginx_status { - stub_status on; - access_log off; - include common/acl.conf; -} -location ~ ^/(status|ping)$ { - include fastcgi_params; - fastcgi_pass php; - include common/acl.conf; -} -# EasyEngine (ee) utilities -# phpMyAdmin settings -location = /pma { - return 301 https://$host:22222/db/pma; -} -location = /phpMyAdmin { - return 301 https://$host:22222/db/pma; -} -location = /phpmyadmin { - return 301 https://$host:22222/db/pma; -} -# Adminer settings -location = /adminer { - return 301 https://$host:22222/db/adminer; -} diff --git a/ee/cli/templates/nginx-core.mustache b/ee/cli/templates/nginx-core.mustache deleted file mode 100644 index bdce6d2d1..000000000 --- a/ee/cli/templates/nginx-core.mustache +++ /dev/null @@ -1,60 +0,0 @@ -## -# EasyEngine Settings -## - -server_tokens off; -reset_timedout_connection on; -add_header X-Powered-By "EasyEngine {{version}}"; -add_header rt-Fastcgi-Cache $upstream_cache_status; - -# Limit Request -limit_req_status 403; -limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; - -# Proxy Settings -# set_real_ip_from proxy-server-ip; -# real_ip_header X-Forwarded-For; - -fastcgi_read_timeout 300; -client_max_body_size 100m; - -# SSL Settings -ssl_session_cache shared:SSL:20m; -ssl_session_timeout 10m; -{{#Ubuntu}} -ssl_prefer_server_ciphers on; -{{/Ubuntu}} -ssl_ciphers HIGH:!aNULL:!MD5:!kEDH; - -# Log format Settings -log_format rt_cache '$remote_addr $upstream_response_time $upstream_cache_status [$time_local] ' -'$http_host "$request" $status $body_bytes_sent ' -'"$http_referer" "$http_user_agent"'; - - -# GZip settings -gzip_vary on; -gzip_proxied any; -gzip_comp_level 6; -gzip_buffers 16 8k; -gzip_http_version 1.1; -# Compress all output labeled with one of the following MIME-types. -gzip_types - application/atom+xml - application/javascript - application/json - application/rss+xml - application/vnd.ms-fontobject - application/x-font-ttf - application/x-web-app-manifest+json - application/xhtml+xml - application/xml - font/opentype - image/svg+xml - image/x-icon - text/css - text/plain - text/x-component - text/xml - text/javascript; - # text/html is always compressed by HttpGzipModule diff --git a/ee/cli/templates/pagespeed-common.mustache b/ee/cli/templates/pagespeed-common.mustache deleted file mode 100644 index 5b789c8dc..000000000 --- a/ee/cli/templates/pagespeed-common.mustache +++ /dev/null @@ -1,44 +0,0 @@ -# HTTPS Support -# pagespeed FetchHttps enable; - -# PageSpeed Filters -# CSS Minification -# pagespeed EnableFilters combine_css,rewrite_css; - -# JS Minification -# pagespeed EnableFilters combine_javascript,rewrite_javascript; - -# Images Optimization -#pagespeed EnableFilters lazyload_images; -#pagespeed EnableFilters rewrite_images; -#pagespeed EnableFilters convert_jpeg_to_progressive,convert_png_to_jpeg,convert_jpeg_to_webp,convert_to_webp_lossless; - -# Remove comments from HTML -#pagespeed EnableFilters remove_comments; -# Remove WHITESPACE from HTML -#pagespeed EnableFilters collapse_whitespace; - - -# CDN Support -#pagespeed MapRewriteDomain cdn.example.com www.example.com; - - -########################################################################################################################### -# DO NOT EDIT AFTER THIS LINE # -########################################################################################################################### - -# PageSpeed Admin -location /ngx_pagespeed_statistics { include common/acl.conf; } -location /ngx_pagespeed_global_statistics { include common/acl.conf; } -location /ngx_pagespeed_message { include common/acl.conf; } -location /pagespeed_console { include common/acl.conf; } -location ~ ^/pagespeed_admin { include common/acl.conf; } -location ~ ^/pagespeed_global_admin { include common/acl.conf; } - -# Ensure requests for pagespeed optimized resources go to the pagespeed handler -# and no extraneous headers get set. -location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" { - add_header "" ""; -} -location ~ "^/pagespeed_static/" { } -location ~ "^/ngx_pagespeed_beacon$" { } diff --git a/ee/cli/templates/pagespeed-global.mustache b/ee/cli/templates/pagespeed-global.mustache deleted file mode 100644 index bb5ab51eb..000000000 --- a/ee/cli/templates/pagespeed-global.mustache +++ /dev/null @@ -1,21 +0,0 @@ -# Turning the module on and off -pagespeed on; - -# Configuring PageSpeed Filters -pagespeed RewriteLevel PassThrough; - -# Needs to exist and be writable by nginx. Use tmpfs for best performance. -pagespeed MemcachedServers "127.0.0.1:11211"; -pagespeed FileCachePath /var/ngx_pagespeed_cache; - -# PageSpeed Admin -pagespeed StatisticsPath /ngx_pagespeed_statistics; -pagespeed GlobalStatisticsPath /ngx_pagespeed_global_statistics; -pagespeed MessagesPath /ngx_pagespeed_message; -pagespeed ConsolePath /pagespeed_console; -pagespeed AdminPath /pagespeed_admin; -pagespeed GlobalAdminPath /pagespeed_global_admin; - -# PageSpeed Cache Purge -pagespeed EnableCachePurge on; -pagespeed PurgeMethod PURGE; diff --git a/ee/cli/templates/php-fpm.mustache b/ee/cli/templates/php-fpm.mustache deleted file mode 100644 index eae67a415..000000000 --- a/ee/cli/templates/php-fpm.mustache +++ /dev/null @@ -1,5 +0,0 @@ -[global] -pid = {{pid}} -error_log = {{error_log}} -log_level = notice -include = {{include}} \ No newline at end of file diff --git a/ee/cli/templates/php-hhvm.mustache b/ee/cli/templates/php-hhvm.mustache deleted file mode 100644 index 198d81de1..000000000 --- a/ee/cli/templates/php-hhvm.mustache +++ /dev/null @@ -1,10 +0,0 @@ -# PHP NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -location / { - try_files $uri $uri/ /index.php?$args; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass hhvm; -} diff --git a/ee/cli/templates/php.mustache b/ee/cli/templates/php.mustache deleted file mode 100644 index b12b250aa..000000000 --- a/ee/cli/templates/php.mustache +++ /dev/null @@ -1,10 +0,0 @@ -# PHP NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -location / { - try_files $uri $uri/ /index.php?$args; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php; -} diff --git a/ee/cli/templates/php7.mustache b/ee/cli/templates/php7.mustache deleted file mode 100644 index 85f3e44b4..000000000 --- a/ee/cli/templates/php7.mustache +++ /dev/null @@ -1,10 +0,0 @@ -# PHP NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -location / { - try_files $uri $uri/ /index.php?$args; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php7; -} diff --git a/ee/cli/templates/redis-hhvm.mustache b/ee/cli/templates/redis-hhvm.mustache deleted file mode 100644 index 22abdd2ba..000000000 --- a/ee/cli/templates/redis-hhvm.mustache +++ /dev/null @@ -1,58 +0,0 @@ -# Redis NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $skip_cache 0; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $skip_cache 1; -} -if ($query_string != "") { - set $skip_cache 1; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $skip_cache 1; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $skip_cache 1; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files $uri $uri/ /index.php?$args; -} - -location /redis-fetch { - internal ; - set $redis_key $args; - redis_pass redis; -} -location /redis-store { - internal ; - set_unescape_uri $key $arg_key ; - redis2_query set $key $echo_request_body; - redis2_query expire $key 14400; - redis2_pass redis; - -} - -location ~ \.php$ { - set $key "nginx-cache:$scheme$request_method$host$request_uri"; - try_files $uri =404; - - srcache_fetch_skip $skip_cache; - srcache_store_skip $skip_cache; - - srcache_response_cache_control off; - - set_escape_uri $escaped_key $key; - - srcache_fetch GET /redis-fetch $key; - srcache_store PUT /redis-store key=$escaped_key; - - more_set_headers 'X-SRCache-Fetch-Status $srcache_fetch_status'; - more_set_headers 'X-SRCache-Store-Status $srcache_store_status'; - - include fastcgi_params; - fastcgi_param HTTP_ACCEPT_ENCODING ""; - fastcgi_pass hhvm; -} diff --git a/ee/cli/templates/redis-php7.mustache b/ee/cli/templates/redis-php7.mustache deleted file mode 100644 index a3dede557..000000000 --- a/ee/cli/templates/redis-php7.mustache +++ /dev/null @@ -1,56 +0,0 @@ -# Redis NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $skip_cache 0; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $skip_cache 1; -} -if ($query_string != "") { - set $skip_cache 1; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $skip_cache 1; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $skip_cache 1; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files $uri $uri/ /index.php?$args; -} - -location /redis-fetch { - internal ; - set $redis_key $args; - redis_pass redis; -} -location /redis-store { - internal ; - set_unescape_uri $key $arg_key ; - redis2_query set $key $echo_request_body; - redis2_query expire $key 14400; - redis2_pass redis; -} - -location ~ \.php$ { - set $key "nginx-cache:$scheme$request_method$host$request_uri"; - try_files $uri =404; - - srcache_fetch_skip $skip_cache; - srcache_store_skip $skip_cache; - - srcache_response_cache_control off; - - set_escape_uri $escaped_key $key; - - srcache_fetch GET /redis-fetch $key; - srcache_store PUT /redis-store key=$escaped_key; - - more_set_headers 'X-SRCache-Fetch-Status $srcache_fetch_status'; - more_set_headers 'X-SRCache-Store-Status $srcache_store_status'; - - include fastcgi_params; - fastcgi_pass php7; -} diff --git a/ee/cli/templates/redis.mustache b/ee/cli/templates/redis.mustache deleted file mode 100644 index 82c353b31..000000000 --- a/ee/cli/templates/redis.mustache +++ /dev/null @@ -1,57 +0,0 @@ -# Redis NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $skip_cache 0; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $skip_cache 1; -} -if ($query_string != "") { - set $skip_cache 1; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $skip_cache 1; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $skip_cache 1; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files $uri $uri/ /index.php?$args; -} - -location /redis-fetch { - internal ; - set $redis_key $args; - redis_pass redis; -} -location /redis-store { - internal ; - set_unescape_uri $key $arg_key ; - redis2_query set $key $echo_request_body; - redis2_query expire $key 14400; - redis2_pass redis; - -} - -location ~ \.php$ { - set $key "nginx-cache:$scheme$request_method$host$request_uri"; - try_files $uri =404; - - srcache_fetch_skip $skip_cache; - srcache_store_skip $skip_cache; - - srcache_response_cache_control off; - - set_escape_uri $escaped_key $key; - - srcache_fetch GET /redis-fetch $key; - srcache_store PUT /redis-store key=$escaped_key; - - more_set_headers 'X-SRCache-Fetch-Status $srcache_fetch_status'; - more_set_headers 'X-SRCache-Store-Status $srcache_store_status'; - - include fastcgi_params; - fastcgi_pass php; -} diff --git a/ee/cli/templates/siteinfo.mustache b/ee/cli/templates/siteinfo.mustache deleted file mode 100644 index 3d0acbb6f..000000000 --- a/ee/cli/templates/siteinfo.mustache +++ /dev/null @@ -1,15 +0,0 @@ -Information about {{domain}}: - -Nginx configuration {{type}} {{enable}} -{{#php_version}}PHP Version {{php_version}}{{/php_version}} -{{#hhvm}}HHVM {{hhvm}}{{/hhvm}} -{{#ssl}}SSL {{ssl}}{{/ssl}} -{{#sslprovider}}SSL PROVIDER {{sslprovider}}{{/sslprovider}} -{{#sslexpiry}}SSL EXPIRY DATE {{sslexpiry}}{{/sslexpiry}} -access_log {{accesslog}} -error_log {{errorlog}} -{{#webroot}}Webroot {{webroot}}{{/webroot}} -{{#dbname}}DB_NAME {{dbname}}{{/dbname}} -{{#dbname}}DB_USER {{dbuser}}{{/dbname}} -{{#dbname}}DB_PASS {{dbpass}}{{/dbname}} -{{#tablepref}}table_prefix {{tableprefix}}{{/tablepref}} diff --git a/ee/cli/templates/upstream.mustache b/ee/cli/templates/upstream.mustache deleted file mode 100644 index 473fd1bc6..000000000 --- a/ee/cli/templates/upstream.mustache +++ /dev/null @@ -1,26 +0,0 @@ -# Common upstream settings -upstream php { -# server unix:/run/php5-fpm.sock; -server 127.0.0.1:{{php}}; -} -upstream debug { -# Debug Pool -server 127.0.0.1:{{debug}}; -} -{{#php7conf}} -upstream php7 { -server 127.0.0.1:{{php7}}; -} -upstream debug7 { -# Debug Pool -server 127.0.0.1:{{debug7}}; -} -{{/php7conf}} - -{{#hhvmconf}} -upstream hhvm { -# HHVM Pool -server 127.0.0.1:{{hhvm}}; -server 127.0.0.1:{{php}} backup; -} -{{/hhvmconf}} diff --git a/ee/cli/templates/vimbadmin.mustache b/ee/cli/templates/vimbadmin.mustache deleted file mode 100644 index 808d57cef..000000000 --- a/ee/cli/templates/vimbadmin.mustache +++ /dev/null @@ -1,662 +0,0 @@ -;; This file is licenesed Under GNU GENERAL PUBLIC LICENSE Version 3 -;; © Copyright 2011 - 2014 Open Source Solutions Limited, Dublin, Ireland. -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; ViMbAdmin :: Virtual Mailbox Admin -;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; IMPORTANT: Review and change all options in [user] -;; -;; ** This is for ViMbAdmin V3 and later ** -;; -;; See: https://github.com/opensolutions/ViMbAdmin/wiki/Configuration - -[user] - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Installation Keys and Salts -; -; During installation, you will be prompted to enter strings here. This -; is to verify that you are in fact the person authorised to complete the -; installation as well as provide security for cookies and passwords. - -securitysalt = "{{salt}}" -resources.auth.oss.rememberme.salt = "{{salt}}" -defaults.mailbox.password_salt = "{{salt}}" - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; When installing for the first time, it may be useful to set the following -; to 1 BUT ensure you set it to zero again in a production system - -phpSettings.display_startup_errors = 0 -phpSettings.display_errors = 0 -resources.frontController.params.displayExceptions = 0 - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; You database and caching connection. -;; - -resources.doctrine2.connection.options.driver = 'mysqli' -resources.doctrine2.connection.options.dbname = 'vimbadmin' -resources.doctrine2.connection.options.user = 'vimbadmin' -resources.doctrine2.connection.options.password = '{{password}}' -resources.doctrine2.connection.options.host = '{{host}}' -resources.doctrine2.connection.options.charset = 'utf8' - -;; Doctrine2 requires Memcache for maximum efficency. Without Memcache -;; it can be highly inefficient and will slow page requests down. -;; -;; You are strongly advised to install memcache and comment ArrayCache -;; here and uncomment MemcacheCache. -;; - -resources.doctrine2cache.type = 'ArrayCache' -;resources.doctrine2cache.type = 'MemcacheCache' -;resources.doctrine2cache.memcache.servers.0.host = '127.0.0.1' -resources.doctrine2cache.namespace = 'ViMbAdmin3' - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Default values used when creating domains -; -; See: https://github.com/opensolutions/ViMbAdmin/wiki/Configuration -; See: https://github.com/opensolutions/ViMbAdmin/wiki/Quotas - -defaults.domain.quota = 0 -defaults.domain.maxquota = 0 -defaults.domain.transport = "virtual" -defaults.domain.aliases = 0 -defaults.domain.mailboxes = 0 - -defaults.quota.multiplier = 'MB' - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Use server side filtering to reduce pagination time on client side -;; Defaults to off / false -defaults.server_side.pagination.enable = false -defaults.server_side.pagination.min_search_str = 3 -defaults.server_side.pagination.max_result_cnt = 500 - -;; Separate configuration for domain list -defaults.server_side.pagination.domain.enable = false -defaults.server_side.pagination.domain.min_search_str = 3 -defaults.server_side.pagination.domain.max_result_cnt = 500 - -; The number of rows displayed in the tables -; must be one of these: 10, 25, 50, 100 -defaults.table.entries = 50 - - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Options for the display of domain and mailbox sizes -;; -;; See: https://github.com/opensolutions/ViMbAdmin/wiki/Mailbox-Sizes -;; -;; Enable or disable display of sizes. Default: disabled - -defaults.list_size.disabled = true - -;; Maildir size units. By default: KB. One of B, KB, MB or GB. -defaults.list_size.multiplier = 'GB' - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Default values for creating mailboxes - -; This sets the uid and gid columns in the mailbox table to the below values -defaults.mailbox.uid = 5000 -defaults.mailbox.gid = 5000 - - -; Set the homedir and maildir values in the mailbox table where the -; following substitutions apply: -; -; %d -> domain part of email address -; %u -> user part of email address -; $m -> full email address -; -; -; http://wiki2.dovecot.org/VirtualUsers/Home - -defaults.mailbox.maildir = "maildir:/var/vmail/%d/%u" -defaults.mailbox.homedir = "/var/vmail/" - -;minimum mailbox password length -defaults.mailbox.min_password_length = 8 - -; The password hashing function to use. Set to one of: -; -; "plain" - password stored as clear text -; "md5" - password hashed using MD5 without salt (PHP md5()) -; "md5.salted" - password hashed using MD5 with salt (see below) -; "sha1" - password hashed using sha1 without salt -; "sha1.salted" - password hashed using sha1 with salt defined below -; "crypt:XXX" - call the PHP crypt function (with random salt) where XXX is one of: md5, blowfish, sha256, sha512 -; "dovecot:XXX" - call the Dovecot password generator (see next option below) and use the -; scheme specified by XXX. To see available schemes, use 'dovecotpw -l' -; or 'doveadm pw -l' - -defaults.mailbox.password_scheme = "md5" - -; The path to (and initial option(s) if necessary) the Dovecot password generator. Typical -; values may be something like: -; -; "/usr/bin/doveadm pw" -; "/usr/bin/dovecotpw" - -defaults.mailbox.dovecot_pw_binary = "/usr/bin/doveadm pw" - - - -;; A "mailbox alias" will, for example add the following entry to -;; the alias table for a mailbox: name@example.com -;; -;; name@example.com -> name@example.com -;; -;; This is required for aliasing an entire domain. If in doubt, leave it enabled. -mailboxAliases = 1 - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; See: https://github.com/opensolutions/ViMbAdmin/wiki/Archiving-Mailboxes - -server_id = 1 - -;;Archive options -binary.path.chown_R = "/bin/chown -R" -binary.path.tar_cf = "/bin/tar -cf" -binary.path.tar_xf = "/bin/tar -xf" -binary.path.bzip2_q = "/bin/bzip2 -q" -binary.path.bunzip2_q = "/bin/bunzip2 -q" -binary.path.rm_rf = "/bin/rm -rf" - -archive.path = "/srv/archives" - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Enable mailbox deletion on the file system -; -; See: https://github.com/opensolutions/ViMbAdmin/wiki/Deleting-Mailboxes -; - -mailbox_deletion_fs_enabled = false - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Export Mailbox Settings -; -; See: https://github.com/opensolutions/ViMbAdmin/wiki/Export-Settings -; -defaults.export_settings.disabled = true - - -;; Export settings alowed subnets -defaults.export_settings.allowed_subnet[] = "10." -defaults.export_settings.allowed_subnet[] = "192.168." - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Settings email default values. -;; -;; Substituions are as follows: -;; -;; %d -> domain part of email address -;; %u -> user part of email address -;; $m -> full email address -;; -;; See (and skin) the following file to see how the below are used: -;; -;; views/mailbox/email/settings.phtml -;; - -server.smtp.enabled = 1 -server.smtp.host = "mail.%d" -server.smtp.user = "%m" -server.smtp.port = "465" -server.smtp.crypt = "SSL" - -server.pop3.enabled = 1 -server.pop3.host = "gpo.%d" -server.pop3.user = "%m" -server.pop3.port = "995" -server.pop3.crypt = "SSL" - -server.imap.enabled = 1 -server.imap.host = "gpo.%d" -server.imap.user = "%m" -server.imap.port = "993" -server.imap.crypt = "SSL" - -server.webmail.enabled = 1 -server.webmail.host = "https://webmail.%d" -server.webmail.user = "%m" - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Identity - -identity.orgname = "Example Limited" -identity.name = "Example Support Team" -identity.email = "support@example.com" -identity.autobot.name = "ViMbAdmin Autobot" -identity.autobot.email = "autobot@example.com" -identity.mailer.name = "ViMbAdmin Autobot" -identity.mailer.email = "do-not-reply@example.com" - -identity.sitename = "ViMbAdmin" -identity.siteurl = "https://www.example.com/vimbadmin/" - - -;; -;; All mail and correspondence will come from the following;; - -server.email.name = "ViMbAdmin Administrator" -server.email.address = "support@example.com" - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Skinning -;; -;; You can skin ViMbAdmin pages if you wish. -;; -;; See: https://github.com/opensolutions/ViMbAdmin/wiki/Skinning - -; resources.smarty.skin = "myskin" - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; See: http://framework.zend.com/manual/en/zend.mail.smtp-authentication.html -;; -;; Ensure you have a working mail server configuration so the system can -;; send emails: -;; -resources.mailer.smtphost = "localhost" -;resources.mailer.username = "" -;resources.mailer.password = "" -;resources.mailer.auth = "" -;resources.mailer.ssl = "" -;resources.mailer.port = "25" - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Local filesystem logging. -;; -;; We log various things to var/log/YYYY/MM/ if you enable the logger here. -;; -;; It is useful to use the email logger to be alerted of serious errors. -;; - -ondemand_resources.logger.enabled = 1 - -;ondemand_resources.logger.writers.email.from = "admin@example.com" -;ondemand_resources.logger.writers.email.to = "admin@example.com" -;ondemand_resources.logger.writers.email.prefix = "ViMbAdmin_Error" -;ondemand_resources.logger.writers.email.level = 3 - -ondemand_resources.logger.writers.stream.level = 7 - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; ViMbAdmin performs a version check on administrator login and alerts the -;; user if there is a newer version available. -;; -;; This can be disabled by setting the below to 1 -;; - -skipVersionCheck = 0 - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; ViMbAdmin 'pings' the developers as part of the set up process to let -;; them know there is a new installation. -;; -;; All we are interested in is knowing whether people are using the software -;; or not and whether continued support and development is worth the time -;; and effort. -;; -;; Unless you're very shy, PLEASE LET US KNOW YOU'RE USING IT! -;; -;; This can be disabled by setting the below to 1 -;; - -skipInstallPingback = 0 - - - - - - - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Allow admins to dictate whether a user can use BOTH, IMAP ONLY, -; POP3 ONLY when creating mailboxes. -; -; Must be supported by your POP3/IMAP server. -; -; See https://github.com/opensolutions/ViMbAdmin/wiki/POP3-IMAP-Access-Permissions -; for documentation. -; -; This is handled via a plugin -; - -vimbadmin_plugins.AccessPermissions.disabled = false - -; specify the options which should be allowed for access restrictions -vimbadmin_plugins.AccessPermissions.type.SMTP = "SMTP" -vimbadmin_plugins.AccessPermissions.type.IMAP = "IMAP" -vimbadmin_plugins.AccessPermissions.type.POP3 = "POP3" -vimbadmin_plugins.AccessPermissions.type.SIEVE = "SIEVE" - - - - - - - - - - - - - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Proceed onwards with caution. -;; -;; The above [user] params are the may ones of consequence. -;; - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Allows to add additional information. -; -; This is handled via a plugin -; - -vimbadmin_plugins.AccessPermissions.disabled = false -vimbadmin_plugins.Jabber.disabled = true -vimbadmin_plugins.DirectoryEntry.disabled = true -vimbadmin_plugins.SharedMailbox.disabled = true -vimbadmin_plugins.SOGo.disabled = true - - -vimbadmin_plugins.AdditionalInfo.disabled = true -vimbadmin_plugins.Addressbook.disabled = true -vimbadmin_plugins.Calendar.disabled = true -vimbadmin_plugins.RoundCube.disabled = true - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; Disabling directory entry subform element -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -vimbadmin_plugins.DirectoryEntry.disabled_elements.JpegPhoto = true -vimbadmin_plugins.DirectoryEntry.disabled_elements.Mail = true -vimbadmin_plugins.DirectoryEntry.disabled_elements.PreferredLanguage = true -vimbadmin_plugins.DirectoryEntry.disabled_elements.Secretary = true - -vimbadmin_plugins.DirectoryEntry.disabled_elements.PersonalTitle = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.GivenName = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.Sn = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.DisplayName = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.Initials = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.BusinesCategory = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.EmployeeType = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.Title = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.DepartmentNumber = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.Ou = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.RoomNumber = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.O = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.CarLicense = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.EmployeeNumber = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.HomePhone = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.TelephoneNumber = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.Mobile = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.Pager = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.FacsimileTelephoneNumber = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.HomePostalAddress = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.LabeledUri = false -vimbadmin_plugins.DirectoryEntry.disabled_elements.Manager = false - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Mailbox AdditionalInfo plugin elements -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -;;Additional text messages for plugin. -AdditionalInfo.mailbox.formPreBlurb = "

NB: Do not edit the following. It is sync'd on a nightly basis ..." - -; First Name -vimbadmin_plugins.AdditionalInfo.elements.id.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.id.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.id.options.label = "LDAP Id" - -; First Name -vimbadmin_plugins.AdditionalInfo.elements.first_name.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.first_name.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.first_name.options.label = "First Name" - -; Last Name -vimbadmin_plugins.AdditionalInfo.elements.second_name.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.second_name.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.second_name.options.label = "Last Name" - -; Grade -vimbadmin_plugins.AdditionalInfo.elements.grade.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.grade.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.grade.options.label = "Grade" - -; Grade Id -vimbadmin_plugins.AdditionalInfo.elements.grade_id.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.grade_id.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.grade_id.options.label = "Grade Id" -vimbadmin_plugins.AdditionalInfo.elements.grade_id.options.validators.digits[] = 'Digits' -vimbadmin_plugins.AdditionalInfo.elements.grade_id.options.validators.digits[] = true - -; Department -vimbadmin_plugins.AdditionalInfo.elements.department.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.department.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.department.options.label = "Department" - -; Department Id -vimbadmin_plugins.AdditionalInfo.elements.department_id.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.department_id.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.department_id.options.label = "Department Id" -vimbadmin_plugins.AdditionalInfo.elements.department_id.options.validators.digits[] = 'Digits' -vimbadmin_plugins.AdditionalInfo.elements.department_id.options.validators.digits[] = true - -; Section -vimbadmin_plugins.AdditionalInfo.elements.section.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.section.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.section.options.label = "Section" - -; Extension Number -vimbadmin_plugins.AdditionalInfo.elements.ext_no.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.label = "Extension Number" -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.validators.digits[] = 'Digits' -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.validators.digits[] = true -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.validators.length[] = 'StringLength' -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.validators.length[] = false -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.validators.length.range[] = 4 -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.validators.length.range[] = 4 -;;to disable autocomplete functionality -vimbadmin_plugins.AdditionalInfo.elements.ext_no.options.autocomplete = 'off' - -; Direct Dial -vimbadmin_plugins.AdditionalInfo.elements.d_dial.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.d_dial.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.d_dial.options.label = "Direct Dial" -vimbadmin_plugins.AdditionalInfo.elements.d_dial.options.autocomplete = 'off' - -; Mobile -vimbadmin_plugins.AdditionalInfo.elements.mobile.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.elements.mobile.options.required = false -vimbadmin_plugins.AdditionalInfo.elements.mobile.options.label = "Mobile" -vimbadmin_plugins.AdditionalInfo.elements.mobile.options.autocomplete = 'off' - -;;;;;;; -;; Aliases additional information -;; -; First Name -vimbadmin_plugins.AdditionalInfo.alias.elements.name.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.alias.elements.name.options.required = false -vimbadmin_plugins.AdditionalInfo.alias.elements.name.options.label = "Name" - -; Extension Number -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.required = false -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.label = "Extension Number" -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.validators.digits[] = 'Digits' -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.validators.digits[] = true -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.validators.length[] = 'StringLength' -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.validators.length[] = false -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.validators.length.range[] = 4 -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.validators.length.range[] = 4 -vimbadmin_plugins.AdditionalInfo.alias.elements.ext_no.options.autocomplete = 'off' - -; Direct Dial -vimbadmin_plugins.AdditionalInfo.alias.elements.d_dial.type = "Zend_Form_Element_Text" -vimbadmin_plugins.AdditionalInfo.alias.elements.d_dial.options.required = false -vimbadmin_plugins.AdditionalInfo.alias.elements.d_dial.options.label = "Direct Dial" -vimbadmin_plugins.AdditionalInfo.alias.elements.d_dial.options.autocomplete = 'off' - - -[production : user] - -includePaths.library = APPLICATION_PATH "/../library" -includePaths.osslibrary = APPLICATION_PATH "/../vendor/opensolutions/oss-framework/src/" - -bootstrap.path = APPLICATION_PATH "/Bootstrap.php" -bootstrap.class = "Bootstrap" -appnamespace = "ViMbAdmin" - -temporary_directory = APPLICATION_PATH "/../var/tmp" - -pluginPaths.OSS_Resource = APPLICATION_PATH "/../vendor/opensolutions/oss-framework/src/OSS/Resource" -pluginPaths.ViMbAdmin_Resource = APPLICATION_PATH "/../library/ViMbAdmin/Resource" - -mini_js = 1 -mini_css = 1 - -alias_autocomplete_min_length = 2 - - - -resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" -resources.frontController.moduleDirectory = APPLICATION_PATH "/modules" -resources.modules[] = - - -; doctrine2 -resources.doctrine2.models_path = APPLICATION_PATH -resources.doctrine2.proxies_path = APPLICATION_PATH "/Proxies" -resources.doctrine2.repositories_path = APPLICATION_PATH -resources.doctrine2.xml_schema_path = APPLICATION_PATH "/../doctrine2/xml" -resources.doctrine2.autogen_proxies = 0 -resources.doctrine2.logger = 1 -resources.doctrine2.models_namespace = "Entities" -resources.doctrine2.proxies_namespace = "Proxies" -resources.doctrine2.repositories_namespace = "Repositories" - - -resources.doctrine2cache.autoload_method = "composer" -;resources.doctrine2cache.type = 'ArrayCache' -;resources.doctrine2cache.type = 'MemcacheCache' -;resources.doctrine2cache.memcache.servers.0.host = '127.0.0.1' -;resources.doctrine2cache.memcache.servers.0.port = '11211' -;resources.doctrine2cache.memcache.servers.0.persistent = false -;resources.doctrine2cache.memcache.servers.0.weight = 1 -;resources.doctrine2cache.memcache.servers.0.timeout = 1 -;resources.doctrine2cache.memcache.servers.0.retry_int = 15 - -; resources.doctrine2cache.memcache.servers.1.host = 'xxx' -; resources.doctrine2cache.memcache.servers.2.host = 'yyy' - -resources.namespace.checkip = 0 - -resources.auth.enabled = 1 -resources.auth.oss.adapter = "OSS_Auth_Doctrine2Adapter" -resources.auth.oss.pwhash = "bcrypt" -resources.auth.oss.hash_cost = 9 -resources.auth.oss.entity = "\\Entities\\Admin" -resources.auth.oss.disabled.lost-username = 1 -resources.auth.oss.disabled.lost-password = 0 - -resources.auth.oss.rememberme.enabled = 1 -resources.auth.oss.rememberme.timeout = 2592000 -resources.auth.oss.rememberme.secure = true - -resources.auth.oss.lost_password.use_captcha = true - -resources.session.save_path = APPLICATION_PATH "/../var/session" -resources.session.use_only_cookies = true -resources.session.remember_me_seconds = 3600 -resources.session.name = 'VIMBADMIN3' - -ondemand_resources.logger.writers.stream.path = APPLICATION_PATH "/../var/log" -ondemand_resources.logger.writers.stream.owner = {{php_user}} -ondemand_resources.logger.writers.stream.group = {{php_user}} -ondemand_resources.logger.writers.stream.mode = single -ondemand_resources.logger.writers.stream.logname = vimbadmin.log - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Smarty View -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -resources.smarty.enabled = 1 -resources.smarty.templates = APPLICATION_PATH "/views" -; resources.smarty.skin = "myskin" -resources.smarty.compiled = APPLICATION_PATH "/../var/templates_c" -resources.smarty.cache = APPLICATION_PATH "/../var/cache" -resources.smarty.config = APPLICATION_PATH "/configs/smarty" -resources.smarty.plugins[] = APPLICATION_PATH "/../library/ViMbAdmin/Smarty/functions" -resources.smarty.plugins[] = APPLICATION_PATH "/../vendor/opensolutions/oss-framework/src/OSS/Smarty/functions" -resources.smarty.plugins[] = APPLICATION_PATH "/../vendor/smarty/smarty/libs/plugins" -resources.smarty.plugins[] = APPLICATION_PATH "/../vendor/smarty/smarty/libs/sysplugins" -resources.smarty.debugging = 0 - - - - -[development : production] - -mini_js = 0 -mini_css = 0 - -phpSettings.display_startup_errors = 1 -phpSettings.display_errors = 1 -resources.frontController.params.displayExceptions = 1 diff --git a/ee/cli/templates/virtual_alias_maps.mustache b/ee/cli/templates/virtual_alias_maps.mustache deleted file mode 100644 index b7919c74d..000000000 --- a/ee/cli/templates/virtual_alias_maps.mustache +++ /dev/null @@ -1,5 +0,0 @@ -user = vimbadmin -password = {{password}} -hosts = {{host}} -dbname = vimbadmin -query = SELECT goto FROM alias WHERE address = '%s' AND active = '1' diff --git a/ee/cli/templates/virtual_domains_maps.mustache b/ee/cli/templates/virtual_domains_maps.mustache deleted file mode 100644 index 2dce79a0d..000000000 --- a/ee/cli/templates/virtual_domains_maps.mustache +++ /dev/null @@ -1,5 +0,0 @@ -user = vimbadmin -password = {{password}} -hosts = {{host}} -dbname = vimbadmin -query = SELECT domain FROM domain WHERE domain = '%s' AND backupmx = '0' AND active = '1' diff --git a/ee/cli/templates/virtual_mailbox_maps.mustache b/ee/cli/templates/virtual_mailbox_maps.mustache deleted file mode 100644 index f8aafdaf8..000000000 --- a/ee/cli/templates/virtual_mailbox_maps.mustache +++ /dev/null @@ -1,7 +0,0 @@ -user = vimbadmin -password = {{password}} -hosts = {{host}} -dbname = vimbadmin -table = mailbox -select_field = maildir -where_field = username diff --git a/ee/cli/templates/virtualconf-php7.mustache b/ee/cli/templates/virtualconf-php7.mustache deleted file mode 100644 index 681eb35bf..000000000 --- a/ee/cli/templates/virtualconf-php7.mustache +++ /dev/null @@ -1,49 +0,0 @@ - -server { - - {{#multisite}} - # Uncomment the following line for domain mapping - # listen 80 default_server; - {{/multisite}} - - server_name {{^vma}}{{^rc}}{{site_name}}{{/rc}}{{/vma}} {{#vma}}vma.*{{/vma}} {{#rc}}webmail.*{{/rc}} {{^vma}}{{^rc}}{{#multisite}}*{{/multisite}}{{^multisite}}www{{/multisite}}.{{site_name}}{{/rc}}{{/vma}}; - - {{#multisite}} - # Uncomment the following line for domain mapping - #server_name_in_redirect off; - {{/multisite}} - - access_log /var/log/nginx/{{site_name}}.access.log {{^wpredis}}{{^static}}rt_cache{{/static}}{{/wpredis}}{{#wpredis}}rt_cache_redis{{/wpredis}}; - error_log /var/log/nginx/{{site_name}}.error.log; - - {{#proxy}} - add_header X-Proxy-Cache $upstream_cache_status; - location / { - proxy_pass http://{{host}}:{{port}}; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - {{/proxy}} - - {{^proxy}} - {{^vma}}{{^rc}}root {{webroot}}/htdocs;{{/rc}}{{/vma}} - {{#vma}}root /var/www/22222/htdocs/vimbadmin/public;{{/vma}} - {{#rc}}root /var/www/roundcubemail/htdocs/;{{/rc}} - - {{^proxy}}index {{^static}}index.php{{/static}} index.html index.htm;{{/proxy}} - - {{#static}} - location / { - try_files $uri $uri/ =404; - } - {{/static}} - - {{^static}}include {{^hhvm}}{{#basic}}common/php7.conf;{{/basic}}{{#w3tc}}common/w3tc-php7.conf;{{/w3tc}}{{#wpfc}}common/wpfc-php7.conf;{{/wpfc}} {{#wpsc}}common/wpsc-php7.conf;{{/wpsc}}{{#wpredis}}common/redis-php7.conf;{{/wpredis}} {{/hhvm}}{{#hhvm}}{{#basic}}common/php-hhvm.conf;{{/basic}}{{#w3tc}}common/w3tc-hhvm.conf;{{/w3tc}}{{#wpfc}}common/wpfc-hhvm.conf;{{/wpfc}} {{#wpsc}}common/wpsc-hhvm.conf;{{/wpsc}}{{#wpredis}}common/redis-hhvm.conf;{{/wpredis}} {{/hhvm}} - {{#wpsubdir}}include common/wpsubdir.conf;{{/wpsubdir}}{{/static}} - {{#wp}}include common/wpcommon-php7.conf;{{/wp}} - {{^proxy}}include common/locations-php7.conf;{{/proxy}} - {{^vma}}{{^rc}}include {{webroot}}/conf/nginx/*.conf;{{/rc}}{{/vma}} - {{/proxy}} -} diff --git a/ee/cli/templates/virtualconf.mustache b/ee/cli/templates/virtualconf.mustache deleted file mode 100644 index c7d3ac438..000000000 --- a/ee/cli/templates/virtualconf.mustache +++ /dev/null @@ -1,48 +0,0 @@ - -server { - - {{#multisite}} - # Uncomment the following line for domain mapping - # listen 80 default_server; - {{/multisite}} - - server_name {{^vma}}{{^rc}}{{site_name}}{{/rc}}{{/vma}} {{#vma}}vma.*{{/vma}} {{#rc}}webmail.*{{/rc}} {{^vma}}{{^rc}}{{#multisite}}*{{/multisite}}{{^multisite}}www{{/multisite}}.{{site_name}}{{/rc}}{{/vma}}; - - {{#multisite}} - # Uncomment the following line for domain mapping - #server_name_in_redirect off; - {{/multisite}} - - access_log /var/log/nginx/{{site_name}}.access.log {{^wpredis}}{{^static}}rt_cache{{/static}}{{/wpredis}}{{#wpredis}}rt_cache_redis{{/wpredis}}; - error_log /var/log/nginx/{{site_name}}.error.log; - - {{#proxy}} - add_header X-Proxy-Cache $upstream_cache_status; - location / { - proxy_pass http://{{host}}:{{port}}; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - {{/proxy}} - - {{^proxy}} - {{^vma}}{{^rc}}root {{webroot}}/htdocs;{{/rc}}{{/vma}} - {{#vma}}root /var/www/22222/htdocs/vimbadmin/public;{{/vma}} - {{#rc}}root /var/www/roundcubemail/htdocs/;{{/rc}} - - {{^proxy}}index {{^static}}index.php{{/static}} index.html index.htm;{{/proxy}} - - {{#static}} - location / { - try_files $uri $uri/ =404; - } - {{/static}} - - {{^static}}include {{^hhvm}}{{#basic}}common/php.conf;{{/basic}}{{#w3tc}}common/w3tc.conf;{{/w3tc}}{{#wpfc}}common/wpfc.conf;{{/wpfc}} {{#wpsc}}common/wpsc.conf;{{/wpsc}}{{#wpredis}}common/redis.conf;{{/wpredis}} {{/hhvm}}{{#hhvm}}{{#basic}}common/php-hhvm.conf;{{/basic}}{{#w3tc}}common/w3tc-hhvm.conf;{{/w3tc}}{{#wpfc}}common/wpfc-hhvm.conf;{{/wpfc}} {{#wpsc}}common/wpsc-hhvm.conf;{{/wpsc}}{{#wpredis}}common/redis-hhvm.conf;{{/wpredis}} {{/hhvm}} {{#wpsubdir}}include common/wpsubdir.conf;{{/wpsubdir}}{{/static}} - {{#wp}}include common/wpcommon.conf;{{/wp}} - {{^proxy}}include common/locations.conf;{{/proxy}} - {{^vma}}{{^rc}}include {{webroot}}/conf/nginx/*.conf;{{/rc}}{{/vma}} - {{/proxy}} -} diff --git a/ee/cli/templates/w3tc-hhvm.mustache b/ee/cli/templates/w3tc-hhvm.mustache deleted file mode 100644 index 86b13eefd..000000000 --- a/ee/cli/templates/w3tc-hhvm.mustache +++ /dev/null @@ -1,31 +0,0 @@ - -# W3TC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $cache_uri $request_uri; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $cache_uri 'null cache'; -} -if ($query_string != "") { - set $cache_uri 'null cache'; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $cache_uri 'null cache'; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $cache_uri 'null cache'; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files /wp-content/cache/page_enhanced/${host}${cache_uri}_index.html $uri $uri/ /index.php?$args; -} -location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { - try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass hhvm; -} diff --git a/ee/cli/templates/w3tc-php7.mustache b/ee/cli/templates/w3tc-php7.mustache deleted file mode 100644 index 802847661..000000000 --- a/ee/cli/templates/w3tc-php7.mustache +++ /dev/null @@ -1,31 +0,0 @@ - -# W3TC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $cache_uri $request_uri; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $cache_uri 'null cache'; -} -if ($query_string != "") { - set $cache_uri 'null cache'; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $cache_uri 'null cache'; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $cache_uri 'null cache'; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files /wp-content/cache/page_enhanced/${host}${cache_uri}_index.html $uri $uri/ /index.php?$args; -} -location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { - try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php7; -} diff --git a/ee/cli/templates/w3tc.mustache b/ee/cli/templates/w3tc.mustache deleted file mode 100644 index daff74848..000000000 --- a/ee/cli/templates/w3tc.mustache +++ /dev/null @@ -1,31 +0,0 @@ - -# W3TC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $cache_uri $request_uri; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $cache_uri 'null cache'; -} -if ($query_string != "") { - set $cache_uri 'null cache'; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $cache_uri 'null cache'; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $cache_uri 'null cache'; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files /wp-content/cache/page_enhanced/${host}${cache_uri}_index.html $uri $uri/ /index.php?$args; -} -location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { - try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php; -} diff --git a/ee/cli/templates/wpcommon-php7.mustache b/ee/cli/templates/wpcommon-php7.mustache deleted file mode 100644 index b09437247..000000000 --- a/ee/cli/templates/wpcommon-php7.mustache +++ /dev/null @@ -1,21 +0,0 @@ -# WordPress COMMON SETTINGS -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -# Limit access to avoid brute force attack -location = /wp-login.php { - limit_req zone=one burst=1 nodelay; - include fastcgi_params; - fastcgi_pass php7; -} -# Disable wp-config.txt -location = /wp-config.txt { - deny all; - access_log off; - log_not_found off; -} -# Disallow php in upload folder -location /wp-content/uploads/ { - location ~ \.php$ { - #Prevent Direct Access Of PHP Files From Web Browsers - deny all; - } -} diff --git a/ee/cli/templates/wpcommon.mustache b/ee/cli/templates/wpcommon.mustache deleted file mode 100644 index 394c68725..000000000 --- a/ee/cli/templates/wpcommon.mustache +++ /dev/null @@ -1,21 +0,0 @@ -# WordPress COMMON SETTINGS -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -# Limit access to avoid brute force attack -location = /wp-login.php { - limit_req zone=one burst=1 nodelay; - include fastcgi_params; - fastcgi_pass php; -} -# Disable wp-config.txt -location = /wp-config.txt { - deny all; - access_log off; - log_not_found off; -} -# Disallow php in upload folder -location /wp-content/uploads/ { - location ~ \.php$ { - #Prevent Direct Access Of PHP Files From Web Browsers - deny all; - } -} diff --git a/ee/cli/templates/wpfc-hhvm.mustache b/ee/cli/templates/wpfc-hhvm.mustache deleted file mode 100644 index 5d268bbb5..000000000 --- a/ee/cli/templates/wpfc-hhvm.mustache +++ /dev/null @@ -1,37 +0,0 @@ -# WPFC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $skip_cache 0; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $skip_cache 1; -} -if ($query_string != "") { - set $skip_cache 1; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $skip_cache 1; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $skip_cache 1; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files $uri $uri/ /index.php?$args; -} -location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { - try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass hhvm; - fastcgi_cache_bypass $skip_cache; - fastcgi_no_cache $skip_cache; - fastcgi_cache WORDPRESS; -} -location ~ /purge(/.*) { - fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; - access_log off; -} diff --git a/ee/cli/templates/wpfc-php7.mustache b/ee/cli/templates/wpfc-php7.mustache deleted file mode 100644 index 5ab0f783e..000000000 --- a/ee/cli/templates/wpfc-php7.mustache +++ /dev/null @@ -1,37 +0,0 @@ -# WPFC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $skip_cache 0; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $skip_cache 1; -} -if ($query_string != "") { - set $skip_cache 1; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $skip_cache 1; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $skip_cache 1; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files $uri $uri/ /index.php?$args; -} -location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { - try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php7; - fastcgi_cache_bypass $skip_cache; - fastcgi_no_cache $skip_cache; - fastcgi_cache WORDPRESS; -} -location ~ /purge(/.*) { - fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; - access_log off; -} diff --git a/ee/cli/templates/wpfc.mustache b/ee/cli/templates/wpfc.mustache deleted file mode 100644 index 46430138d..000000000 --- a/ee/cli/templates/wpfc.mustache +++ /dev/null @@ -1,37 +0,0 @@ -# WPFC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $skip_cache 0; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $skip_cache 1; -} -if ($query_string != "") { - set $skip_cache 1; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $skip_cache 1; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $skip_cache 1; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - try_files $uri $uri/ /index.php?$args; -} -location ~ ^/wp-content/cache/minify/(.+\.(css|js))$ { - try_files $uri /wp-content/plugins/w3-total-cache/pub/minify.php?file=$1; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php; - fastcgi_cache_bypass $skip_cache; - fastcgi_no_cache $skip_cache; - fastcgi_cache WORDPRESS; -} -location ~ /purge(/.*) { - fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; - access_log off; -} diff --git a/ee/cli/templates/wpsc-hhvm.mustache b/ee/cli/templates/wpsc-hhvm.mustache deleted file mode 100644 index f99a82f34..000000000 --- a/ee/cli/templates/wpsc-hhvm.mustache +++ /dev/null @@ -1,31 +0,0 @@ -# WPSC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $cache_uri $request_uri; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $cache_uri 'null cache'; -} -if ($query_string != "") { - set $cache_uri 'null cache'; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $cache_uri 'null cache'; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $cache_uri 'null cache'; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - # If we add index.php?$args its break WooCommerce like plugins - # Ref: #330 - try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass hhvm; - # Following line is needed by WP Super Cache plugin - fastcgi_param SERVER_NAME $http_host; -} diff --git a/ee/cli/templates/wpsc-php7.mustache b/ee/cli/templates/wpsc-php7.mustache deleted file mode 100644 index 9b2225405..000000000 --- a/ee/cli/templates/wpsc-php7.mustache +++ /dev/null @@ -1,31 +0,0 @@ -# WPSC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $cache_uri $request_uri; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $cache_uri 'null cache'; -} -if ($query_string != "") { - set $cache_uri 'null cache'; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $cache_uri 'null cache'; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $cache_uri 'null cache'; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - # If we add index.php?$args its break WooCommerce like plugins - # Ref: #330 - try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php7; - # Following line is needed by WP Super Cache plugin - fastcgi_param SERVER_NAME $http_host; -} diff --git a/ee/cli/templates/wpsc.mustache b/ee/cli/templates/wpsc.mustache deleted file mode 100644 index 9e864ec87..000000000 --- a/ee/cli/templates/wpsc.mustache +++ /dev/null @@ -1,31 +0,0 @@ -# WPSC NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -set $cache_uri $request_uri; -# POST requests and URL with a query string should always go to php -if ($request_method = POST) { - set $cache_uri 'null cache'; -} -if ($query_string != "") { - set $cache_uri 'null cache'; -} -# Don't cache URL containing the following segments -if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|wp-.*\.php|index.php|/feed/|.*sitemap.*\.xml)") { - set $cache_uri 'null cache'; -} -# Don't use the cache for logged in users or recent commenter or customer with items in cart -if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|[a-z0-9]+_items_in_cart") { - set $cache_uri 'null cache'; -} -# Use cached or actual file if they exists, Otherwise pass request to WordPress -location / { - # If we add index.php?$args its break WooCommerce like plugins - # Ref: #330 - try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php; -} -location ~ \.php$ { - try_files $uri =404; - include fastcgi_params; - fastcgi_pass php; - # Following line is needed by WP Super Cache plugin - fastcgi_param SERVER_NAME $http_host; -} diff --git a/ee/cli/templates/wpsubdir.mustache b/ee/cli/templates/wpsubdir.mustache deleted file mode 100644 index 2ef064895..000000000 --- a/ee/cli/templates/wpsubdir.mustache +++ /dev/null @@ -1,10 +0,0 @@ -# WPSUBDIRECTORY NGINX CONFIGURATION -# DO NOT MODIFY, ALL CHANGES LOST AFTER UPDATE EasyEngine (ee) -if (!-e $request_filename) { - # Redirect wp-admin to wp-admin/ - rewrite /wp-admin$ $scheme://$host$uri/ permanent; - # Redirect wp-* files/folders - rewrite ^(/[^/]+)?(/wp-.*) $2 last; - # Redirect other php files - rewrite ^(/[^/]+)?(/.*\.php) $2 last; -} diff --git a/ee/core/addswap.py b/ee/core/addswap.py deleted file mode 100644 index de2e4eb04..000000000 --- a/ee/core/addswap.py +++ /dev/null @@ -1,48 +0,0 @@ -"""EasyEngine SWAP creation""" -from ee.core.variables import EEVariables -from ee.core.shellexec import EEShellExec -from ee.core.fileutils import EEFileUtils -from ee.core.aptget import EEAptGet -from ee.core.logging import Log -import os - - -class EESwap(): - """Manage Swap""" - - def __init__(): - """Initialize """ - pass - - def add(self): - """Swap addition with EasyEngine""" - if EEVariables.ee_ram < 512: - if EEVariables.ee_swap < 1000: - Log.info(self, "Adding SWAP file, please wait...") - - # Install dphys-swapfile - EEAptGet.update(self) - EEAptGet.install(self, ["dphys-swapfile"]) - # Stop service - EEShellExec.cmd_exec(self, "service dphys-swapfile stop") - # Remove Default swap created - EEShellExec.cmd_exec(self, "/sbin/dphys-swapfile uninstall") - - # Modify Swap configuration - if os.path.isfile("/etc/dphys-swapfile"): - EEFileUtils.searchreplace(self, "/etc/dphys-swapfile", - "#CONF_SWAPFILE=/var/swap", - "CONF_SWAPFILE=/ee-swapfile") - EEFileUtils.searchreplace(self, "/etc/dphys-swapfile", - "#CONF_MAXSWAP=2048", - "CONF_MAXSWAP=1024") - EEFileUtils.searchreplace(self, "/etc/dphys-swapfile", - "#CONF_SWAPSIZE=", - "CONF_SWAPSIZE=1024") - else: - with open("/etc/dphys-swapfile", 'w') as conffile: - conffile.write("CONF_SWAPFILE=/ee-swapfile\n" - "CONF_SWAPSIZE=1024\n" - "CONF_MAXSWAP=1024\n") - # Create swap file - EEShellExec.cmd_exec(self, "service dphys-swapfile start") diff --git a/ee/core/apt_repo.py b/ee/core/apt_repo.py deleted file mode 100644 index c743cfdf5..000000000 --- a/ee/core/apt_repo.py +++ /dev/null @@ -1,91 +0,0 @@ -"""EasyEngine packages repository operations""" -from ee.core.shellexec import EEShellExec -from ee.core.variables import EEVariables -from ee.core.logging import Log -import os - - -class EERepo(): - """Manage Repositories""" - - def __init__(self): - """Initialize """ - pass - - def add(self, repo_url=None, ppa=None): - """ - This function used to add apt repositories and or ppa's - If repo_url is provided adds repo file to - /etc/apt/sources.list.d/ - If ppa is provided add apt-repository using - add-apt-repository - command. - """ - - if repo_url is not None: - repo_file_path = ("/etc/apt/sources.list.d/" - + EEVariables().ee_repo_file) - try: - if not os.path.isfile(repo_file_path): - with open(repo_file_path, - encoding='utf-8', mode='a') as repofile: - repofile.write(repo_url) - repofile.write('\n') - repofile.close() - elif repo_url not in open(repo_file_path, - encoding='utf-8').read(): - with open(repo_file_path, - encoding='utf-8', mode='a') as repofile: - repofile.write(repo_url) - repofile.write('\n') - repofile.close() - return True - except IOError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "File I/O error.") - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to add repo") - if ppa is not None: - EEShellExec.cmd_exec(self, "add-apt-repository -y '{ppa_name}'" - .format(ppa_name=ppa)) - - def remove(self, ppa=None, repo_url=None): - """ - This function used to remove ppa's - If ppa is provided adds repo file to - /etc/apt/sources.list.d/ - command. - """ - if ppa: - EEShellExec.cmd_exec(self, "add-apt-repository -y " - "--remove '{ppa_name}'" - .format(ppa_name=ppa)) - elif repo_url: - repo_file_path = ("/etc/apt/sources.list.d/" - + EEVariables().ee_repo_file) - - try: - repofile = open(repo_file_path, "w+") - repofile.write(repofile.read().replace(repo_url, "")) - repofile.close() - except IOError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "File I/O error.") - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to remove repo") - - def add_key(self, keyids, keyserver=None): - """ - This function adds imports repository keys from keyserver. - default keyserver is hkp://keys.gnupg.net - user can provide other keyserver with keyserver="hkp://xyz" - """ - EEShellExec.cmd_exec(self, "gpg --keyserver {serv}" - .format(serv=(keyserver or - "hkp://keys.gnupg.net")) - + " --recv-keys {key}".format(key=keyids)) - EEShellExec.cmd_exec(self, "gpg -a --export --armor {0}" - .format(keyids) - + " | apt-key add - ") diff --git a/ee/core/aptget.py b/ee/core/aptget.py deleted file mode 100644 index 6555ed8b9..000000000 --- a/ee/core/aptget.py +++ /dev/null @@ -1,231 +0,0 @@ -"""EasyEngine package installation using apt-get module.""" -import apt -import apt_pkg -import sys -import subprocess -from ee.core.logging import Log -from ee.core.apt_repo import EERepo -from sh import apt_get -from sh import ErrorReturnCode - - -class EEAptGet(): - """Generic apt-get intialisation""" - - def update(self): - """ - Similar to `apt-get update` - """ - try: - with open('/var/log/ee/ee.log', 'a') as f: - proc = subprocess.Popen('apt-get update', - shell=True, - stdin=None, stdout=f, - stderr=subprocess.PIPE, - executable="/bin/bash") - proc.wait() - output, error_output = proc.communicate() - - # Check what is error in error_output - if "NO_PUBKEY" in str(error_output): - # Split the output - Log.info(self, "Fixing missing GPG keys, please wait...") - error_list = str(error_output).split("\\n") - - # Use a loop to add misising keys - for single_error in error_list: - if "NO_PUBKEY" in single_error: - key = single_error.rsplit(None, 1)[-1] - EERepo.add_key(self, key, keyserver="hkp://pgp.mit.edu") - - proc = subprocess.Popen('apt-get update', - shell=True, - stdin=None, stdout=f, stderr=f, - executable="/bin/bash") - proc.wait() - - if proc.returncode == 0: - return True - else: - Log.info(self, Log.FAIL + "Oops Something went wrong!!") - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - except Exception as e: - Log.error(self, "apt-get update exited with error") - - def check_upgrade(self): - """ - Similar to `apt-get upgrade` - """ - try: - check_update = subprocess.Popen(['apt-get upgrade -s | grep ' - '\"^Inst\" | wc -l'], - stdout=subprocess.PIPE, - shell=True).communicate()[0] - if check_update == b'0\n': - Log.error(self, "No package updates available") - Log.info(self, "Following package updates are available:") - subprocess.Popen("apt-get -s dist-upgrade | grep \"^Inst\"", - shell=True, executable="/bin/bash", - stdout=sys.stdout).communicate() - - except Exception as e: - Log.error(self, "Unable to check for packages upgrades") - - def dist_upgrade(self): - """ - Similar to `apt-get upgrade` - """ - try: - with open('/var/log/ee/ee.log', 'a') as f: - proc = subprocess.Popen("DEBIAN_FRONTEND=noninteractive " - "apt-get dist-upgrade -o " - "Dpkg::Options::=\"--force-confdef\"" - " -o " - "Dpkg::Options::=\"--force-confold\"" - " -y ", - shell=True, stdin=None, - stdout=f, stderr=f, - executable="/bin/bash") - proc.wait() - - if proc.returncode == 0: - return True - else: - Log.info(self, Log.FAIL + "Oops Something went " - "wrong!!") - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - except Exception as e: - Log.error(self, "Error while installing packages, " - "apt-get exited with error") - - def install(self, packages): - all_packages = ' '.join(packages) - try: - with open('/var/log/ee/ee.log', 'a') as f: - proc = subprocess.Popen("DEBIAN_FRONTEND=noninteractive " - "apt-get install -o " - "Dpkg::Options::=\"--force-confdef\"" - " -o " - "Dpkg::Options::=\"--force-confold\"" - " -y --allow-unauthenticated {0}" - .format(all_packages), shell=True, - stdin=None, stdout=f, stderr=f, - executable="/bin/bash") - proc.wait() - - if proc.returncode == 0: - return True - else: - Log.info(self, Log.FAIL + "Oops Something went " - "wrong!!") - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - except Exception as e: - Log.info(self, Log.FAIL + "Oops Something went " - "wrong!!") - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - def remove(self, packages, auto=False, purge=False): - all_packages = ' '.join(packages) - try: - with open('/var/log/ee/ee.log', 'a') as f: - if purge: - proc = subprocess.Popen('apt-get purge -y {0}' - .format(all_packages), shell=True, - stdin=None, stdout=f, stderr=f, - executable="/bin/bash") - else: - proc = subprocess.Popen('apt-get remove -y {0}' - .format(all_packages), shell=True, - stdin=None, stdout=f, stderr=f, - executable="/bin/bash") - proc.wait() - if proc.returncode == 0: - return True - else: - Log.info(self, Log.FAIL + "Oops Something went " - "wrong!!") - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - except Exception as e: - Log.error(self, "Error while installing packages, " - "apt-get exited with error") - - def auto_clean(self): - """ - Similar to `apt-get autoclean` - """ - try: - orig_out = sys.stdout - sys.stdout = open(self.app.config.get('log.logging', 'file'), - encoding='utf-8', mode='a') - apt_get.autoclean("-y") - sys.stdout = orig_out - except ErrorReturnCode as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to apt-get autoclean") - - def auto_remove(self): - """ - Similar to `apt-get autoremove` - """ - try: - Log.debug(self, "Running apt-get autoremove") - apt_get.autoremove("-y") - except ErrorReturnCode as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to apt-get autoremove") - - def is_installed(self, package_name): - """ - Checks if package is available in cache and is installed or not - returns True if installed otherwise returns False - """ - apt_cache = apt.cache.Cache() - apt_cache.open() - if (package_name.strip() in apt_cache and - apt_cache[package_name.strip()].is_installed): - # apt_cache.close() - return True - # apt_cache.close() - return False - - def download_only(self,package_name,repo_url=None,repo_key=None): - """ - Similar to `apt-get install --download-only PACKAGE_NAME` - """ - packages = ' '.join(package_name) - try: - with open('/var/log/ee/ee.log', 'a') as f: - if repo_url is not None: - EERepo.add(self, repo_url=repo_url) - if repo_key is not None: - EERepo.add_key(self, repo_key) - proc = subprocess.Popen("apt-get update && DEBIAN_FRONTEND=noninteractive " - "apt-get install -o " - "Dpkg::Options::=\"--force-confdef\"" - " -o " - "Dpkg::Options::=\"--force-confold\"" - " -y --download-only {0}" - .format(packages), shell=True, - stdin=None, stdout=f, stderr=f, - executable="/bin/bash") - proc.wait() - - if proc.returncode == 0: - return True - else: - Log.error(self,"Error in fetching dpkg package.\nReverting changes ..",False) - if repo_url is not None: - EERepo.remove(self, repo_url=repo_url) - return False - except Exception as e: - Log.error(self, "Error while downloading packages, " - "apt-get exited with error") - diff --git a/ee/core/checkfqdn.py b/ee/core/checkfqdn.py deleted file mode 100644 index 4caf93806..000000000 --- a/ee/core/checkfqdn.py +++ /dev/null @@ -1,23 +0,0 @@ -from ee.core.shellexec import EEShellExec -from ee.core.variables import EEVariables -import os - - -def check_fqdn(self, ee_host): - """FQDN check with EasyEngine, for mail server hostname must be FQDN""" - # ee_host=os.popen("hostname -f | tr -d '\n'").read() - if '.' in ee_host: - EEVariables.ee_fqdn = ee_host - with open('/etc/hostname', encoding='utf-8', mode='w') as hostfile: - hostfile.write(ee_host) - - EEShellExec.cmd_exec(self, "sed -i \"1i\\127.0.0.1 {0}\" /etc/hosts" - .format(ee_host)) - if EEVariables.ee_platform_distro == 'debian': - EEShellExec.cmd_exec(self, "/etc/init.d/hostname.sh start") - else: - EEShellExec.cmd_exec(self, "service hostname restart") - - else: - ee_host = input("Enter hostname [fqdn]:") - check_fqdn(self, ee_host) diff --git a/ee/core/cron.py b/ee/core/cron.py deleted file mode 100644 index 7357ebd1c..000000000 --- a/ee/core/cron.py +++ /dev/null @@ -1,32 +0,0 @@ -from ee.core.shellexec import EEShellExec -from ee.core.logging import Log - -""" -Set CRON on LINUX system. -""" - -class EECron(): - def setcron_weekly(self,cmd,comment='Cron set by EasyEngine',user='root',min=0,hour=12): - if not EEShellExec.cmd_exec(self, "crontab -l | grep -q \'{0}\'".format(cmd)): - - EEShellExec.cmd_exec(self, "/bin/bash -c \"crontab -l " - "2> /dev/null | {{ cat; echo -e" - " \\\"" - "\\n0 0 * * 0 " - "{0}".format(cmd) + - " # {0}".format(comment)+ - "\\\"; } | crontab -\"") - Log.debug(self, "Cron set") - - - - def remove_cron(self,cmd): - if EEShellExec.cmd_exec(self, "crontab -l | grep -q \'{0}\'".format(cmd)): - if not EEShellExec.cmd_exec(self, "/bin/bash -c " - "\"crontab " - "-l | sed '/{0}/d'" - "| crontab -\"" - .format(cmd)): - Log.error(self, "Failed to remove crontab entry",False) - else: - Log.debug(self, "Cron not found") diff --git a/ee/core/database.py b/ee/core/database.py deleted file mode 100644 index 0b44eac1d..000000000 --- a/ee/core/database.py +++ /dev/null @@ -1,28 +0,0 @@ -"""EasyEngine generic database creation module""" -from sqlalchemy import create_engine -from sqlalchemy.orm import scoped_session, sessionmaker -from sqlalchemy.ext.declarative import declarative_base -from ee.core.variables import EEVariables - -# db_path = self.app.config.get('site', 'db_path') -engine = create_engine(EEVariables.ee_db_uri, convert_unicode=True) -db_session = scoped_session(sessionmaker(autocommit=False, - autoflush=False, - bind=engine)) -Base = declarative_base() -Base.query = db_session.query_property() - - -def init_db(app): - """ - Initializes and creates all tables from models into the database - """ - # import all modules here that might define models so that - # they will be registered properly on the metadata. Otherwise - # # you will have to import them first before calling init_db() - # import ee.core.models - try: - app.log.info("Initializing EasyEngine Database") - Base.metadata.create_all(bind=engine) - except Exception as e: - app.log.debug("{0}".format(e)) diff --git a/ee/core/domainvalidate.py b/ee/core/domainvalidate.py deleted file mode 100644 index 2b1fe4a54..000000000 --- a/ee/core/domainvalidate.py +++ /dev/null @@ -1,24 +0,0 @@ -"""EasyEngine domain validation module.""" -from urllib.parse import urlparse - - -def ValidateDomain(url): - """ - This function returns domain name removing http:// and https:// - returns domain name only with or without www as user provided. - """ - - # Check if http:// or https:// present remove it if present - domain_name = url.split('/') - if 'http:' in domain_name or 'https:' in domain_name: - domain_name = domain_name[2] - else: - domain_name = domain_name[0] - www_domain_name = domain_name.split('.') - final_domain = '' - if www_domain_name[0] == 'www': - final_domain = '.'.join(www_domain_name[1:]) - else: - final_domain = domain_name - - return (final_domain, domain_name) diff --git a/ee/core/download.py b/ee/core/download.py deleted file mode 100644 index e3280111c..000000000 --- a/ee/core/download.py +++ /dev/null @@ -1,45 +0,0 @@ -"""EasyEngine download core classes.""" -import urllib.request -import urllib.error -import os -from ee.core.logging import Log - - -class EEDownload(): - """Method to download using urllib""" - def __init__(): - pass - - def download(self, packages): - """Download packages, packges must be list in format of - [url, path, package name]""" - for package in packages: - url = package[0] - filename = package[1] - pkg_name = package[2] - try: - directory = os.path.dirname(filename) - if not os.path.exists(directory): - os.makedirs(directory) - Log.info(self, "Downloading {0:20}".format(pkg_name), end=' ') - req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) - with urllib.request.urlopen(req) as response, open(filename, 'wb') as out_file: - out_file.write(response.read()) - Log.info(self, "{0}".format("[" + Log.ENDC + "Done" - + Log.OKBLUE + "]")) - except urllib.error.URLError as e: - Log.debug(self, "[{err}]".format(err=str(e.reason))) - Log.error(self, "Unable to download file, {0}" - .format(filename)) - return False - except urllib.error.HTTPError as e: - Log.error(self, "Package download failed. {0}" - .format(pkg_name)) - Log.debug(self, "[{err}]".format(err=str(e.reason))) - return False - except urllib.error.ContentTooShortError as e: - Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) - Log.error(self, "Package download failed. The amount of the" - " downloaded data is less than " - "the expected amount \{0} ".format(pkg_name)) - return False diff --git a/ee/core/exc.py b/ee/core/exc.py deleted file mode 100644 index 06579f41f..000000000 --- a/ee/core/exc.py +++ /dev/null @@ -1,26 +0,0 @@ -"""EasyEngine exception classes.""" - - -class EEError(Exception): - """Generic errors.""" - def __init__(self, msg): - Exception.__init__(self) - self.msg = msg - - def __str__(self): - return self.msg - - -class EEConfigError(EEError): - """Config related errors.""" - pass - - -class EERuntimeError(EEError): - """Generic runtime errors.""" - pass - - -class EEArgumentError(EEError): - """Argument related errors.""" - pass diff --git a/ee/core/extract.py b/ee/core/extract.py deleted file mode 100644 index 59c2c23ac..000000000 --- a/ee/core/extract.py +++ /dev/null @@ -1,21 +0,0 @@ -"""EasyEngine extarct core classes.""" -import tarfile -import os -from ee.core.logging import Log - - -class EEExtract(): - """Method to extract from tar.gz file""" - - def extract(self, file, path): - """Function to extract tar.gz file""" - try: - tar = tarfile.open(file) - tar.extractall(path=path) - tar.close() - os.remove(file) - return True - except tarfile.TarError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, 'Unable to extract file \{0}'.format(file)) - return False diff --git a/ee/core/fileutils.py b/ee/core/fileutils.py deleted file mode 100644 index b5369050f..000000000 --- a/ee/core/fileutils.py +++ /dev/null @@ -1,268 +0,0 @@ -"""EasyEngine file utils core classes.""" -import shutil -import os -import sys -import glob -import shutil -import pwd -import fileinput -from ee.core.logging import Log - - -class EEFileUtils(): - """Utilities to operate on files""" - def __init__(): - pass - - def remove(self, filelist): - """remove files from given path""" - for file in filelist: - if os.path.isfile(file): - Log.info(self, "Removing {0:65}".format(file), end=' ') - os.remove(file) - Log.info(self, "{0}".format("[" + Log.ENDC + "Done" + - Log.OKBLUE + "]")) - Log.debug(self, 'file Removed') - if os.path.isdir(file): - try: - Log.info(self, "Removing {0:65}".format(file), end=' ') - shutil.rmtree(file) - Log.info(self, "{0}".format("[" + Log.ENDC + "Done" + - Log.OKBLUE + "]")) - except shutil.Error as e: - Log.debug(self, "{err}".format(err=str(e.reason))) - Log.error(self, 'Unable to Remove file ') - - def create_symlink(self, paths, errormsg=''): - """ - Create symbolic links provided in list with first as source - and second as destination - """ - src = paths[0] - dst = paths[1] - if not os.path.islink(dst): - try: - Log.debug(self, "Creating Symbolic link, Source:{0}, Dest:{1}" - .format(src, dst)) - os.symlink(src, dst) - except Exception as e: - Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) - Log.error(self, "Unable to create symbolic link ...\n ") - else: - Log.debug(self, "Destination: {0} exists".format(dst)) - - def remove_symlink(self, filepath): - """ - Removes symbolic link for the path provided with filepath - """ - try: - Log.debug(self, "Removing symbolic link: {0}".format(filepath)) - os.unlink(filepath) - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to reomove symbolic link ...\n") - - def copyfiles(self, src, dest): - """ - Copies files: - src : source path - dest : destination path - - Recursively copy an entire directory tree rooted at src. - The destination directory, named by dst, must not already exist; - it will be created as well as missing parent directories. - """ - try: - Log.debug(self, "Copying files, Source:{0}, Dest:{1}" - .format(src, dest)) - shutil.copytree(src, dest) - except shutil.Error as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, 'Unable to copy files from {0} to {1}' - .format(src, dest)) - except IOError as e: - Log.debug(self, "{0}".format(e.strerror)) - Log.error(self, "Unable to copy files from {0} to {1}" - .format(src, dest)) - - def copyfile(self, src, dest): - """ - Copy file: - src : source path - dest : destination path - """ - try: - Log.debug(self, "Copying file, Source:{0}, Dest:{1}" - .format(src, dest)) - shutil.copy2(src, dest) - except shutil.Error as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, 'Unable to copy file from {0} to {1}' - .format(src, dest)) - except IOError as e: - Log.debug(self, "{0}".format(e.strerror)) - Log.error(self, "Unable to copy file from {0} to {1}" - .format(src, dest)) - - def searchreplace(self, fnm, sstr, rstr): - """ - Search replace strings in file - fnm : filename - sstr: search string - rstr: replace string - """ - try: - Log.debug(self, "Doning search and replace, File:{0}," - "Source string:{1}, Dest String:{2}" - .format(fnm, sstr, rstr)) - for line in fileinput.input(fnm, inplace=True): - print(line.replace(sstr, rstr), end='') - fileinput.close() - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to search {0} and replace {1} {2}" - .format(fnm, sstr, rstr)) - - def mvfile(self, src, dst): - """ - Moves file from source path to destination path - src : source path - dst : Destination path - """ - try: - Log.debug(self, "Moving file from {0} to {1}".format(src, dst)) - shutil.move(src, dst) - except Exception as e: - Log.debug(self, "{err}".format(err=e)) - Log.error(self, 'Unable to move file from {0} to {1}' - .format(src, dst)) - - def chdir(self, path): - """ - Change Directory to path specified - Path : path for destination directory - """ - try: - Log.debug(self, "Changing directory to {0}" - .format(path)) - os.chdir(path) - except OSError as e: - Log.debug(self, "{err}".format(err=e.strerror)) - Log.error(self, 'Unable to Change Directory {0}'.format(path)) - - def chown(self, path, user, group, recursive=False): - """ - Change Owner for files - change owner for file with path specified - user: username of owner - group: group of owner - recursive: if recursive is True change owner for all - files in directory - """ - userid = pwd.getpwnam(user)[2] - groupid = pwd.getpwnam(user)[3] - try: - Log.debug(self, "Changing ownership of {0}, Userid:{1},Groupid:{2}" - .format(path, userid, groupid)) - # Change inside files/directory permissions only if recursive flag - # is set - if recursive: - for root, dirs, files in os.walk(path): - for d in dirs: - os.chown(os.path.join(root, d), userid, - groupid) - for f in files: - os.chown(os.path.join(root, f), userid, - groupid) - os.chown(path, userid, groupid) - except shutil.Error as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to change owner : {0}".format(path)) - except Exception as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to change owner : {0} ".format(path)) - - def chmod(self, path, perm, recursive=False): - """ - Changes Permission for files - path : file path permission to be changed - perm : permissions to be given - recursive: change permission recursively for all files - """ - try: - Log.debug(self, "Changing permission of {0}, Perm:{1}" - .format(path, perm)) - if recursive: - for root, dirs, files in os.walk(path): - for d in dirs: - os.chmod(os.path.join(root, d), perm) - for f in files: - os.chmod(os.path.join(root, f), perm) - else: - os.chmod(path, perm) - except OSError as e: - Log.debug(self, "{0}".format(e.strerror)) - Log.error(self, "Unable to change owner : {0}".format(path)) - - def mkdir(self, path): - """ - create directories. - path : path for directory to be created - Similar to `mkdir -p` - """ - try: - Log.debug(self, "Creating directories: {0}" - .format(path)) - os.makedirs(path) - except OSError as e: - Log.debug(self, "{0}".format(e.strerror)) - Log.error(self, "Unable to create directory {0} ".format(path)) - - def isexist(self, path): - """ - Check if file exist on given path - """ - try: - if os.path.exists(path): - return (True) - else: - return (False) - except OSError as e: - Log.debug(self, "{0}".format(e.strerror)) - Log.error(self, "Unable to check path {0}".format(path)) - - def grep(self, fnm, sstr): - """ - Searches for string in file and returns the matched line. - """ - try: - Log.debug(self, "Finding string {0} to file {1}" - .format(sstr, fnm)) - for line in open(fnm, encoding='utf-8'): - if sstr in line: - return line - return False - except OSError as e: - Log.debug(self, "{0}".format(e.strerror)) - Log.error(self, "Unable to Search string {0} in {1}" - .format(sstr, fnm)) - - def rm(self, path): - """ - Remove files - """ - Log.debug(self, "Removing {0}".format(path)) - if EEFileUtils.isexist(self, path): - try: - if os.path.isdir(path): - shutil.rmtree(path) - else: - os.remove(path) - except shutil.Error as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to remove directory : {0} " - .format(path)) - except OSError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to remove file : {0} " - .format(path)) diff --git a/ee/core/git.py b/ee/core/git.py deleted file mode 100644 index 5d587c81d..000000000 --- a/ee/core/git.py +++ /dev/null @@ -1,57 +0,0 @@ -"""EasyEngine GIT module""" -from sh import git, ErrorReturnCode -from ee.core.logging import Log -import os - - -class EEGit: - """Intialization of core variables""" - def ___init__(): - # TODO method for core variables - pass - - def add(self, paths, msg="Intializating"): - """ - Initializes Directory as repository if not already git repo. - and adds uncommited changes automatically - """ - for path in paths: - global git - git = git.bake("--git-dir={0}/.git".format(path), - "--work-tree={0}".format(path)) - if os.path.isdir(path): - if not os.path.isdir(path+"/.git"): - try: - Log.debug(self, "EEGit: git init at {0}" - .format(path)) - git.init(path) - except ErrorReturnCode as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to git init at {0}" - .format(path)) - status = git.status("-s") - if len(status.splitlines()) > 0: - try: - Log.debug(self, "EEGit: git commit at {0}" - .format(path)) - git.add("--all") - git.commit("-am {0}".format(msg)) - except ErrorReturnCode as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "Unable to git commit at {0} " - .format(path)) - else: - Log.debug(self, "EEGit: Path {0} not present".format(path)) - - def checkfilestatus(self, repo, filepath): - """ - Checks status of file, If its tracked or untracked. - """ - global git - git = git.bake("--git-dir={0}/.git".format(repo), - "--work-tree={0}".format(repo)) - status = git.status("-s", "{0}".format(filepath)) - if len(status.splitlines()) > 0: - return True - else: - return False diff --git a/ee/core/logging.py b/ee/core/logging.py deleted file mode 100644 index 1fcb8da7d..000000000 --- a/ee/core/logging.py +++ /dev/null @@ -1,47 +0,0 @@ -"""EasyEngine log module""" - - -class Log: - """ - Logs messages with colors for different messages - according to functions - """ - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' - - def error(self, msg, exit=True): - """ - Logs error into log file - """ - print(Log.FAIL + msg + Log.ENDC) - self.app.log.error(Log.FAIL + msg + Log.ENDC) - if exit: - self.app.close(1) - - def info(self, msg, end='\n', log=True): - """ - Logs info messages into log file - """ - - print(Log.OKBLUE + msg + Log.ENDC, end=end) - if log: - self.app.log.info(Log.OKBLUE + msg + Log.ENDC) - - def warn(self, msg): - """ - Logs warning into log file - """ - print(Log.WARNING + msg + Log.ENDC) - self.app.log.warn(Log.BOLD + msg + Log.ENDC) - - def debug(self, msg): - """ - Logs debug messages into log file - """ - self.app.log.debug(Log.HEADER + msg + Log.ENDC) diff --git a/ee/core/logwatch.py b/ee/core/logwatch.py deleted file mode 100644 index ec2e210f8..000000000 --- a/ee/core/logwatch.py +++ /dev/null @@ -1,195 +0,0 @@ - -""" -Real time log files watcher supporting log rotation. -""" - -import os -import time -import errno -import stat -from ee.core.logging import Log - - -class LogWatcher(object): - """Looks for changes in all files of a directory. - This is useful for watching log file changes in real-time. - It also supports files rotation. - - Example: - - >>> def callback(filename, lines): - ... print filename, lines - ... - >>> l = LogWatcher("/var/www/example.com/logs", callback) - >>> l.loop() - """ - - def __init__(self, filelist, callback, extensions=["log"], tail_lines=0): - """Arguments: - - (str) @folder: - the folder to watch - - (callable) @callback: - a function which is called every time a new line in a - file being watched is found; - this is called with "filename" and "lines" arguments. - - (list) @extensions: - only watch files with these extensions - - (int) @tail_lines: - read last N lines from files being watched before starting - """ - self.files_map = {} - self.filelist = filelist - self.callback = callback - # self.folder = os.path.realpath(folder) - self.extensions = extensions - # assert (os.path.isdir(self.folder), "%s does not exists" - # % self.folder) - for file in self.filelist: - assert (os.path.isfile(file)) - assert callable(callback) - self.update_files() - # The first time we run the script we move all file markers at EOF. - # In case of files created afterwards we don't do this. - for id, file in list(iter(self.files_map.items())): - file.seek(os.path.getsize(file.name)) # EOF - if tail_lines: - lines = self.tail(file.name, tail_lines) - if lines: - self.callback(file.name, lines) - - def __del__(self): - self.close() - - def loop(self, interval=0.1, async=False): - """Start the loop. - If async is True make one loop then return. - """ - while 1: - self.update_files() - for fid, file in list(iter(self.files_map.items())): - self.readfile(file) - if async: - return - time.sleep(interval) - - def log(self, line): - """Log when a file is un/watched""" - print(line) - - # def listdir(self): - # """List directory and filter files by extension. - # You may want to override this to add extra logic or - # globbling support. - # """ - # ls = os.listdir(self.folder) - # if self.extensions: - # return ([x for x in ls if os.path.splitext(x)[1][1:] - # in self.extensions]) - # else: - # return ls - - @staticmethod - def tail(fname, window): - """Read last N lines from file fname.""" - try: - f = open(fname, encoding='utf-8', mode='r') - except IOError as err: - if err.errno == errno.ENOENT: - return [] - else: - raise - else: - BUFSIZ = 1024 - f.seek(0, os.SEEK_END) - fsize = f.tell() - block = -1 - data = "" - exit = False - while not exit: - step = (block * BUFSIZ) - if abs(step) >= fsize: - f.seek(0) - exit = True - else: - f.seek(step, os.SEEK_END) - data = f.read().strip() - if data.count('\n') >= window: - break - else: - block -= 1 - return data.splitlines()[-window:] - - def update_files(self): - ls = [] - for name in self.filelist: - absname = os.path.realpath(os.path.join(name)) - try: - st = os.stat(absname) - except EnvironmentError as err: - if err.errno != errno.ENOENT: - raise - else: - if not stat.S_ISREG(st.st_mode): - continue - fid = self.get_file_id(st) - ls.append((fid, absname)) - - # check existent files - for fid, file in list(iter(self.files_map.items())): - # next(iter(graph.items())) - try: - st = os.stat(file.name) - except EnvironmentError as err: - if err.errno == errno.ENOENT: - self.unwatch(file, fid) - else: - raise - else: - if fid != self.get_file_id(st): - # same name but different file (rotation); reload it. - self.unwatch(file, fid) - self.watch(file.name) - - # add new ones - for fid, fname in ls: - if fid not in self.files_map: - self.watch(fname) - - def readfile(self, file): - lines = file.readlines() - if lines: - self.callback(file.name, lines) - - def watch(self, fname): - try: - file = open(fname, encoding='utf-8', mode='r') - fid = self.get_file_id(os.stat(fname)) - except EnvironmentError as err: - if err.errno != errno.ENOENT: - raise - else: - self.log("watching logfile %s" % fname) - self.files_map[fid] = file - - def unwatch(self, file, fid): - # file no longer exists; if it has been renamed - # try to read it for the last time in case the - # log rotator has written something in it. - lines = self.readfile(file) - self.log("un-watching logfile %s" % file.name) - del self.files_map[fid] - if lines: - self.callback(file.name, lines) - - @staticmethod - def get_file_id(st): - return "%xg%x" % (st.st_dev, st.st_ino) - - def close(self): - for id, file in list(iter(self.files_map.items())): - file.close() - self.files_map.clear() diff --git a/ee/core/mysql.py b/ee/core/mysql.py deleted file mode 100644 index f197d8be4..000000000 --- a/ee/core/mysql.py +++ /dev/null @@ -1,136 +0,0 @@ -"""EasyEngine MySQL core classes.""" -import pymysql -from pymysql import connections, DatabaseError, Error -import configparser -from os.path import expanduser -import sys -import os -from ee.core.logging import Log -from ee.core.variables import EEVariables - - -class MySQLConnectionError(Exception): - """Custom Exception when MySQL server Not Connected""" - pass - - -class StatementExcecutionError(Exception): - """Custom Exception when any Query Fails to execute""" - pass - - -class DatabaseNotExistsError(Exception): - """Custom Exception when Database not Exist""" - pass - - -class EEMysql(): - """Method for MySQL connection""" - - def connect(self): - """Makes connection with MySQL server""" - try: - if os.path.exists('/etc/mysql/conf.d/my.cnf'): - connection = pymysql.connect(read_default_file='/etc/mysql/conf.d/my.cnf') - else: - connection = pymysql.connect(read_default_file='~/.my.cnf') - return connection - except ValueError as e: - Log.debug(self, str(e)) - raise MySQLConnectionError - except pymysql.err.InternalError as e: - Log.debug(self, str(e)) - raise MySQLConnectionError - - def dbConnection(self, db_name): - try: - if os.path.exists('/etc/mysql/conf.d/my.cnf'): - connection = pymysql.connect(db=db_name,read_default_file='/etc/mysql/conf.d/my.cnf') - else: - connection = pymysql.connect(db=db_name,read_default_file='~/.my.cnf') - - return connection - except DatabaseError as e: - if e.args[1] == '#42000Unknown database \'{0}\''.format(db_name): - raise DatabaseNotExistsError - else: - raise MySQLConnectionError - except pymysql.err.InternalError as e: - Log.debug(self, str(e)) - raise MySQLConnectionError - except Exception as e : - Log.debug(self, "[Error]Setting up database: \'" + str(e) + "\'") - raise MySQLConnectionError - - def execute(self, statement, errormsg='', log=True): - """Get login details from /etc/mysql/conf.d/my.cnf & Execute MySQL query""" - connection = EEMysql.connect(self) - log and Log.debug(self, "Exceuting MySQL Statement : {0}" - .format(statement)) - try: - cursor = connection.cursor() - sql = statement - cursor.execute(sql) - - # connection is not autocommit by default. - # So you must commit to save your changes. - connection.commit() - except AttributeError as e: - Log.debug(self, str(e)) - raise StatementExcecutionError - except Error as e: - Log.debug(self, str(e)) - raise StatementExcecutionError - finally: - connection.close() - - def backupAll(self): - import subprocess - try: - Log.info(self, "Backing up database at location: " - "/var/ee-mysqlbackup") - # Setup Nginx common directory - if not os.path.exists('/var/ee-mysqlbackup'): - Log.debug(self, 'Creating directory' - '/var/ee-mysqlbackup') - os.makedirs('/var/ee-mysqlbackup') - - db = subprocess.check_output(["mysql -Bse \'show databases\'"], - universal_newlines=True, - shell=True).split('\n') - for dbs in db: - if dbs == "": - continue - Log.info(self, "Backing up {0} database".format(dbs)) - p1 = subprocess.Popen("mysqldump {0}" - " --max_allowed_packet=1024M" - " --single-transaction".format(dbs), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) - p2 = subprocess.Popen("gzip -c > /var/ee-mysqlbackup/{0}{1}.s" - "ql.gz".format(dbs, EEVariables.ee_date), - stdin=p1.stdout, - shell=True) - - # Allow p1 to receive a SIGPIPE if p2 exits - p1.stdout.close() - output = p1.stderr.read() - p1.wait() - if p1.returncode == 0: - Log.debug(self, "done") - else: - Log.error(self, output.decode("utf-8")) - except Exception as e: - Log.error(self, "Error: process exited with status %s" - % e) - - def check_db_exists(self, db_name): - try: - if EEMysql.dbConnection(self, db_name): - return True - except DatabaseNotExistsError as e: - Log.debug(self, str(e)) - return False - except MySQLConnectionError as e: - Log.debug(self, str(e)) - return False diff --git a/ee/core/nginxhashbucket.py b/ee/core/nginxhashbucket.py deleted file mode 100644 index 862165c2e..000000000 --- a/ee/core/nginxhashbucket.py +++ /dev/null @@ -1,44 +0,0 @@ -"""EasyEngine Hash bucket calculate function for Nginx""" -from ee.core.fileutils import EEFileUtils -import math -import os -import fileinput -import re -import subprocess - - -def hashbucket(self): - # Check Nginx Hashbucket error - sub = subprocess.Popen('nginx -t', stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) - output, error_output = sub.communicate() - if 'server_names_hash_bucket_size' not in str(error_output): - return True - - count = 0 - # Get the list of sites-availble - sites_list = os.listdir("/etc/nginx/sites-enabled/") - - # Count the number of characters in site names - for site in sites_list: - count = sum([count, len(site)]) - - # Calculate Nginx hash bucket size - ngx_calc = math.trunc(sum([math.log(count, 2), 2])) - ngx_hash = math.trunc(math.pow(2, ngx_calc)) - - # Replace hashbucket in Nginx.conf file - if EEFileUtils.grep(self, "/etc/nginx/nginx.conf", - "server_names_hash_bucket_size"): - for line in fileinput.FileInput("/etc/nginx/nginx.conf", inplace=1): - if "server_names_hash_bucket_size" in line: - print("\tserver_names_hash_bucket_size {0};".format(ngx_hash)) - else: - print(line, end='') - - else: - EEFileUtils.searchreplace(self, '/etc/nginx/nginx.conf', - "gzip_disable \"msie6\";", - "gzip_disable \"msie6\";\n" - "\tserver_names_hash_bucket_size {0};\n" - .format(ngx_hash)) diff --git a/ee/core/sendmail.py b/ee/core/sendmail.py deleted file mode 100644 index 21d6cadf0..000000000 --- a/ee/core/sendmail.py +++ /dev/null @@ -1,33 +0,0 @@ -import smtplib -import os -from email.mime.multipart import MIMEMultipart -from email.mime.base import MIMEBase -from email.mime.text import MIMEText -from email.utils import COMMASPACE, formatdate -from email import encoders - - -def EESendMail(send_from, send_to, subject, text, files, server="localhost", - port=587, username='', password='', isTls=True): - msg = MIMEMultipart() - msg['From'] = send_from - msg['To'] = send_to - msg['Date'] = formatdate(localtime=True) - msg['Subject'] = subject - - msg.attach(MIMEText(text)) - - for f in files: - part = MIMEBase('application', "octet-stream") - part.set_payload(open(f, "rb").read()) - encoders.encode_base64(part) - part.add_header('Content-Disposition', 'attachment; filename="{0}"' - .format(os.path.basename(f))) - msg.attach(part) - - smtp = smtplib.SMTP(server, port) - if isTls: - smtp.starttls() - - smtp.sendmail(send_from, send_to, msg.as_string()) - smtp.quit() diff --git a/ee/core/services.py b/ee/core/services.py deleted file mode 100644 index b0c52766c..000000000 --- a/ee/core/services.py +++ /dev/null @@ -1,134 +0,0 @@ -"""EasyEngine service start/stop/restart module.""" -import os -import sys -import subprocess -from subprocess import Popen -from ee.core.logging import Log -import pystache - - -class EEService(): - """Intialization for service""" - def ___init__(): - pass - - def start_service(self, service_name): - """ - start service - Similar to `service xyz start` - """ - try: - if service_name in ['nginx', 'php5-fpm']: - service_cmd = ('{0} -t && service {0} start' - .format(service_name)) - else: - service_cmd = ('service {0} start'.format(service_name)) - - Log.info(self, "Start : {0:10}" .format(service_name), end='') - retcode = subprocess.getstatusoutput(service_cmd) - if retcode[0] == 0: - Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") - return True - else: - Log.debug(self, "{0}".format(retcode[1])) - Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") - return False - except OSError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "\nFailed to start service {0}" - .format(service_name)) - - def stop_service(self, service_name): - """ - Stop service - Similar to `service xyz stop` - """ - try: - Log.info(self, "Stop : {0:10}" .format(service_name), end='') - retcode = subprocess.getstatusoutput('service {0} stop' - .format(service_name)) - if retcode[0] == 0: - Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") - return True - else: - Log.debug(self, "{0}".format(retcode[1])) - Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") - return False - except OSError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "\nFailed to stop service : {0}" - .format(service_name)) - - def restart_service(self, service_name): - """ - Restart service - Similar to `service xyz restart` - """ - try: - if service_name in ['nginx', 'php5-fpm']: - service_cmd = ('{0} -t && service {0} restart' - .format(service_name)) - else: - service_cmd = ('service {0} restart'.format(service_name)) - - Log.info(self, "Restart : {0:10}".format(service_name), end='') - retcode = subprocess.getstatusoutput(service_cmd) - if retcode[0] == 0: - Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") - return True - else: - Log.debug(self, "{0}".format(retcode[1])) - Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") - return False - except OSError as e: - Log.debug(self, "{0} {1}".format(e.errno, e.strerror)) - Log.error(self, "\nFailed to restart service : {0}" - .format(service_name)) - - def reload_service(self, service_name): - """ - Stop service - Similar to `service xyz stop` - """ - try: - if service_name in ['nginx', 'php5-fpm']: - service_cmd = ('{0} -t && service {0} reload' - .format(service_name)) - else: - service_cmd = ('service {0} reload'.format(service_name)) - - Log.info(self, "Reload : {0:10}".format(service_name), end='') - retcode = subprocess.getstatusoutput(service_cmd) - if retcode[0] == 0: - Log.info(self, "[" + Log.ENDC + "OK" + Log.OKBLUE + "]") - return True - else: - Log.debug(self, "{0}".format(retcode[1])) - Log.info(self, "[" + Log.FAIL + "Failed" + Log.OKBLUE+"]") - return False - except OSError as e: - Log.debug(self, "{0}".format(e)) - Log.error(self, "\nFailed to reload service {0}" - .format(service_name)) - - def get_service_status(self, service_name): - - - try: - is_exist = subprocess.getstatusoutput('which {0}' - .format(service_name)) - if is_exist[0] == 0 or service_name in ['php7.0-fpm', 'php5.6-fpm']: - retcode = subprocess.getstatusoutput('service {0} status' - .format(service_name)) - if retcode[0] == 0: - return True - else: - Log.debug(self, "{0}".format(retcode[1])) - return False - else: - return False - except OSError as e: - Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) - Log.error(self, "Unable to get services status of {0}" - .format(service_name)) - return False diff --git a/ee/core/shellexec.py b/ee/core/shellexec.py deleted file mode 100644 index b68076cca..000000000 --- a/ee/core/shellexec.py +++ /dev/null @@ -1,83 +0,0 @@ -"""EasyEngine shell executaion functions.""" -from ee.core.logging import Log -import os -import sys -import subprocess - - -class CommandExecutionError(Exception): - """custom Exception for command execution""" - pass - - -class EEShellExec(): - """Method to run shell commands""" - def __init__(): - pass - - def cmd_exec(self, command, errormsg='', log=True): - """Run shell command from Python""" - try: - log and Log.debug(self, "Running command: {0}".format(command)) - - with subprocess.Popen([command], stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) as proc: - (cmd_stdout_bytes, cmd_stderr_bytes) = proc.communicate() - (cmd_stdout, cmd_stderr) = (cmd_stdout_bytes.decode('utf-8', - "replace"), - cmd_stderr_bytes.decode('utf-8', - "replace")) - - if proc.returncode == 0: - Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" - .format(cmd_stdout, cmd_stderr)) - return True - else: - Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" - .format(cmd_stdout, cmd_stderr)) - return False - except OSError as e: - Log.debug(self, str(e)) - raise CommandExecutionError - except Exception as e: - Log.debug(self, str(e)) - raise CommandExecutionError - - def invoke_editor(self, filepath, errormsg=''): - """ - Open files using sensible editor - """ - try: - subprocess.call(['sensible-editor', filepath]) - except OSError as e: - Log.debug(self, "{0}{1}".format(e.errno, e.strerror)) - raise CommandExecutionError - - - def cmd_exec_stdout(self, command, errormsg='', log=True): - """Run shell command from Python""" - try: - log and Log.debug(self, "Running command: {0}".format(command)) - - with subprocess.Popen([command], stdout=subprocess.PIPE, - stderr=subprocess.PIPE, shell=True) as proc: - (cmd_stdout_bytes, cmd_stderr_bytes) = proc.communicate() - (cmd_stdout, cmd_stderr) = (cmd_stdout_bytes.decode('utf-8', - "replace"), - cmd_stderr_bytes.decode('utf-8', - "replace")) - - if proc.returncode == 0: - Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" - .format(cmd_stdout, cmd_stderr)) - return cmd_stdout - else: - Log.debug(self, "Command Output: {0}, \nCommand Error: {1}" - .format(cmd_stdout, cmd_stderr)) - return cmd_stdout - except OSError as e: - Log.debug(self, str(e)) - raise CommandExecutionError - except Exception as e: - Log.debug(self, str(e)) - raise CommandExecutionError \ No newline at end of file diff --git a/ee/core/sslutils.py b/ee/core/sslutils.py deleted file mode 100644 index d704c71fc..000000000 --- a/ee/core/sslutils.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -from ee.core.shellexec import EEShellExec -from ee.core.logging import Log - - -class SSL: - - def getExpirationDays(self,domain,returnonerror=False): - # check if exist - if not os.path.isfile('/etc/letsencrypt/live/{0}/cert.pem' - .format(domain)): - Log.error(self,'File Not Found : /etc/letsencrypt/live/{0}/cert.pem' - .format(domain),False) - if returnonerror: - return -1 - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - - current_date = EEShellExec.cmd_exec_stdout(self, "date -d \"now\" +%s") - expiration_date = EEShellExec.cmd_exec_stdout(self, "date -d \"`openssl x509 -in /etc/letsencrypt/live/{0}/cert.pem" - " -text -noout|grep \"Not After\"|cut -c 25-`\" +%s".format(domain)) - - days_left = int((int(expiration_date) - int(current_date))/ 86400) - if (days_left > 0): - return days_left - else: - # return "Certificate Already Expired ! Please Renew soon." - return -1 - - def getExpirationDate(self,domain): - # check if exist - if not os.path.isfile('/etc/letsencrypt/live/{0}/cert.pem' - .format(domain)): - Log.error(self,'File Not Found : /etc/letsencrypt/live/{0}/cert.pem' - .format(domain),False) - Log.error(self, "Check logs for reason " - "`tail /var/log/ee/ee.log` & Try Again!!!") - - expiration_date = EEShellExec.cmd_exec_stdout(self, "date -d \"`openssl x509 -in /etc/letsencrypt/live/{0}/cert.pem" - " -text -noout|grep \"Not After\"|cut -c 25-`\" ".format(domain)) - return expiration_date - diff --git a/ee/core/variables.py b/ee/core/variables.py deleted file mode 100644 index bce6e13cf..000000000 --- a/ee/core/variables.py +++ /dev/null @@ -1,230 +0,0 @@ -"""EasyEngine core variable module""" -import platform -import socket -import configparser -import os -import sys -import psutil -import datetime - - -class EEVariables(): - """Intialization of core variables""" - - # EasyEngine version - ee_version = "3.8.1" - # EasyEngine packages versions - ee_wp_cli = "1.5.1" - ee_adminer = "4.6.2" - ee_roundcube = "1.3.6" - # ee_vimbadmin = "3.0.12" - ee_vimbadmin = "master" - - # Get WPCLI path - ee_wpcli_path = os.popen('which wp | tr "\n" " "').read() - if ee_wpcli_path == '': - ee_wpcli_path = '/usr/bin/wp ' - - # Current date and time of System - ee_date = datetime.datetime.now().strftime('%d%b%Y%H%M%S') - - # EasyEngine core variables - ee_platform_distro = platform.linux_distribution()[0].lower() - ee_platform_version = platform.linux_distribution()[1] - ee_platform_codename = os.popen("lsb_release -sc | tr -d \'\\n\'").read() - - # Get timezone of system - if os.path.isfile('/etc/timezone'): - with open("/etc/timezone", "r") as tzfile: - ee_timezone = tzfile.read().replace('\n', '') - if ee_timezone == "Etc/UTC": - ee_timezone = "UTC" - else: - ee_timezone = "UTC" - - # Get FQDN of system - ee_fqdn = socket.getfqdn() - - # EasyEngien default webroot path - ee_webroot = '/var/www/' - - # PHP5 user - ee_php_user = 'www-data' - - # Get git user name and EMail - config = configparser.ConfigParser() - config.read(os.path.expanduser("~")+'/.gitconfig') - try: - ee_user = config['user']['name'] - ee_email = config['user']['email'] - except Exception as e: - ee_user = input("Enter your name: ") - ee_email = input("Enter your email: ") - os.system("git config --global user.name {0}".format(ee_user)) - os.system("git config --global user.email {0}".format(ee_email)) - - # Get System RAM and SWAP details - ee_ram = psutil.virtual_memory().total / (1024 * 1024) - ee_swap = psutil.swap_memory().total / (1024 * 1024) - - # MySQL hostname - ee_mysql_host = "" - config = configparser.RawConfigParser() - if os.path.exists('/etc/mysql/conf.d/my.cnf'): - cnfpath = "/etc/mysql/conf.d/my.cnf" - else: - cnfpath = os.path.expanduser("~")+"/.my.cnf" - if [cnfpath] == config.read(cnfpath): - try: - ee_mysql_host = config.get('client', 'host') - except configparser.NoOptionError as e: - ee_mysql_host = "localhost" - else: - ee_mysql_host = "localhost" - - # EasyEngine stack installation variables - # Nginx repo and packages - if ee_platform_codename == 'precise': - ee_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" - "/rtCamp:/EasyEngine/xUbuntu_12.04/ /") - elif ee_platform_codename == 'trusty': - ee_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" - "/rtCamp:/EasyEngine/xUbuntu_14.04/ /") - elif ee_platform_codename == 'xenial': - ee_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" - "/rtCamp:/EasyEngine/xUbuntu_16.04/ /") - elif ee_platform_codename == 'bionic': - ee_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" - "/rtCamp:/EasyEngine/xUbuntu_18.04/ /") - elif ee_platform_codename == 'wheezy': - ee_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" - "/rtCamp:/EasyEngine/Debian_7.0/ /") - elif ee_platform_codename == 'jessie': - ee_nginx_repo = ("deb http://download.opensuse.org/repositories/home:" - "/rtCamp:/EasyEngine/Debian_8.0/ /") - - - ee_nginx = ["nginx-custom", "nginx-ee"] - ee_nginx_key = '3050AC3CD2AE6F03' - - # PHP repo and packages - if ee_platform_distro == 'ubuntu': - if ee_platform_codename == 'precise': - ee_php_repo = "ppa:ondrej/php5-5.6" - ee_php = ["php5-fpm", "php5-curl", "php5-gd", "php5-imap", - "php5-mcrypt", "php5-common", "php5-readline", - "php5-mysql", "php5-cli", "php5-memcache", "php5-imagick", - "memcached", "graphviz", "php-pear"] - elif (ee_platform_codename == 'trusty' or ee_platform_codename == 'xenial' or ee_platform_codename == 'bionic'): - ee_php_repo = "ppa:ondrej/php" - ee_php5_6 = ["php5.6-fpm", "php5.6-curl", "php5.6-gd", "php5.6-imap", - "php5.6-mcrypt", "php5.6-readline", "php5.6-common", "php5.6-recode", - "php5.6-mysql", "php5.6-cli", "php5.6-curl", "php5.6-mbstring", - "php5.6-bcmath", "php5.6-mysql", "php5.6-opcache", "php5.6-zip", "php5.6-xml", "php5.6-soap"] - ee_php7_0 = ["php7.0-fpm", "php7.0-curl", "php7.0-gd", "php7.0-imap", - "php7.0-mcrypt", "php7.0-readline", "php7.0-common", "php7.0-recode", - "php7.0-cli", "php7.0-mbstring", - "php7.0-bcmath", "php7.0-mysql", "php7.0-opcache", "php7.0-zip", "php7.0-xml", "php7.0-soap"] - ee_php_extra = ["php-memcached", "php-imagick", "php-memcache", "memcached", - "graphviz", "php-pear", "php-xdebug", "php-msgpack", "php-redis"] - elif ee_platform_distro == 'debian': - if ee_platform_codename == 'wheezy': - ee_php_repo = ("deb http://packages.dotdeb.org {codename}-php56 all" - .format(codename=ee_platform_codename)) - else : - ee_php_repo = ("deb http://packages.dotdeb.org {codename} all".format(codename=ee_platform_codename)) - - ee_php = ["php5-fpm", "php5-curl", "php5-gd", "php5-imap", - "php5-mcrypt", "php5-common", "php5-readline", - "php5-mysqlnd", "php5-cli", "php5-memcache", "php5-imagick", - "memcached", "graphviz", "php-pear"] - - ee_php7_0 = ["php7.0-fpm", "php7.0-curl", "php7.0-gd", "php7.0-imap", - "php7.0-mcrypt", "php7.0-common", "php7.0-readline", "php7.0-redis", - "php7.0-mysql", "php7.0-cli", "php7.0-memcache", "php7.0-imagick", - "php7.0-mbstring", "php7.0-recode", "php7.0-bcmath", "php7.0-opcache", "php7.0-zip", "php7.0-xml", - "php7.0-soap", "php7.0-msgpack", - "memcached", "graphviz", "php-pear", "php7.0-xdebug"] - ee_php_extra = [] - - if ee_platform_codename == 'wheezy': - ee_php = ee_php + ["php5-dev"] - - if ee_platform_codename == 'precise' or ee_platform_codename == 'jessie': - ee_php = ee_php + ["php5-xdebug"] - - # MySQL repo and packages - if ee_platform_distro == 'ubuntu': - ee_mysql_repo = ("deb http://sfo1.mirrors.digitalocean.com/mariadb/repo/" - "10.1/ubuntu {codename} main" - .format(codename=ee_platform_codename)) - elif ee_platform_distro == 'debian': - ee_mysql_repo = ("deb http://sfo1.mirrors.digitalocean.com/mariadb/repo/" - "10.1/debian {codename} main" - .format(codename=ee_platform_codename)) - - ee_mysql = ["mariadb-server", "percona-toolkit"] - - # Postfix repo and packages - ee_postfix_repo = "" - ee_postfix = ["postfix"] - - # Mail repo and packages - ee_mail_repo = ("deb http://http.debian.net/debian-backports {codename}" - "-backports main".format(codename=ee_platform_codename)) - if (ee_platform_distro == 'debian' or ee_platform_codename == 'precise'): - ee_mail = ["dovecot-core", "dovecot-imapd", "dovecot-pop3d", - "dovecot-lmtpd", "dovecot-mysql", "dovecot-sieve", - "dovecot-managesieved", "postfix-mysql", "php5-cgi", - "php-gettext", "php-pear"] - else: - ee_mail = ["dovecot-core", "dovecot-imapd", "dovecot-pop3d", - "dovecot-lmtpd", "dovecot-mysql", "dovecot-sieve", - "dovecot-managesieved", "postfix-mysql", "php5.6-cgi", - "php-gettext", "php-pear", "subversion"] - - # Mailscanner repo and packages - ee_mailscanner_repo = () - ee_mailscanner = ["amavisd-new", "spamassassin", "clamav", "clamav-daemon", - "arj", "zoo", "nomarch", "lzop", "cabextract", "p7zip", - "rpm", "unrar-free"] - - # HHVM repo details - # 12.04 requires boot repository - if ee_platform_distro == 'ubuntu': - if ee_platform_codename == "precise": - ee_boost_repo = ("ppa:mapnik/boost") - ee_hhvm_repo = ("deb http://dl.hhvm.com/ubuntu {codename} main" - .format(codename=ee_platform_codename)) - elif ee_platform_codename == "trusty": - ee_hhvm_repo = ("deb http://dl.hhvm.com/ubuntu {codename} main" - .format(codename=ee_platform_codename)) - else: - ee_hhvm_repo = ("deb http://dl.hhvm.com/debian {codename} main" - .format(codename=ee_platform_codename)) - - ee_hhvm = ["hhvm"] - - # Redis repo details - if ee_platform_distro == 'ubuntu': - ee_redis_repo = ("ppa:chris-lea/redis-server") - - else: - ee_redis_repo = ("deb http://packages.dotdeb.org {codename} all" - .format(codename=ee_platform_codename)) - - if (ee_platform_codename == 'trusty' or ee_platform_codename == 'xenial' or ee_platform_codename == 'bionic'): - ee_redis = ['redis-server', 'php-redis'] - else: - ee_redis = ['redis-server', 'php5-redis'] - - # Repo path - ee_repo_file = "ee-repo.list" - ee_repo_file_path = ("/etc/apt/sources.list.d/" + ee_repo_file) - - # Application dabase file path - basedir = os.path.abspath(os.path.dirname('/var/lib/ee/')) - ee_db_uri = 'sqlite:///' + os.path.join(basedir, 'ee.db') - - def __init__(self): - pass diff --git a/ee/utils/test.py b/ee/utils/test.py deleted file mode 100644 index 1d3371edd..000000000 --- a/ee/utils/test.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Testing utilities for EasyEngine.""" -from ee.cli.main import EETestApp -from cement.utils.test import * - - -class EETestCase(CementTestCase): - app_class = EETestApp - - def setUp(self): - """Override setup actions (for every test).""" - super(EETestCase, self).setUp() - - def tearDown(self): - """Override teardown actions (for every test).""" - super(EETestCase, self).tearDown() diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php new file mode 100644 index 000000000..11dfd75db --- /dev/null +++ b/features/bootstrap/FeatureContext.php @@ -0,0 +1,169 @@ +init_logger(); +$runner->init_config(); +/* End. Loading required files to enable EE::launch() in tests. */ + +use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\AfterFeatureScope; + + +use Behat\Gherkin\Node\PyStringNode; + +class FeatureContext implements Context +{ + public $command; + public $webroot_path; + + /** + * @Given ee phar is generated + */ + public function eePharIsPresent() + { + // Checks if phar already exists, replaces it + if(file_exists('ee-old.phar')) { + // Below exec call is required as currenly `ee cli update` is ran with root + // which updates ee.phar with root privileges. + exec("sudo rm ee.phar"); + copy('ee-old.phar','ee.phar'); + return 0; + } + exec("php -dphar.readonly=0 utils/make-phar.php ee.phar", $output, $return_status); + if (0 !== $return_status) { + throw new Exception("Unable to generate phar" . $return_status); + } + + // Cache generaed phar as it is expensive to generate one + copy('ee.phar','ee-old.phar'); + } + + /** + * @Given :command is installed + */ + public function isInstalled($command) + { + exec("type " . $command, $output, $return_status); + if (0 !== $return_status) { + throw new Exception($command . " is not installed! Exit code is:" . $return_status); + } + } + + /** + * @When I run :command + */ + public function iRun($command) + { + $this->command = EE::launch($command, false, true); + } + /** + * @Then return value should be 0 + */ + public function returnCodeShouldBe0() + { + if ( 0 !== $this->command->return_code ) { + throw new Exception("Actual return code is not zero: \n" . $this->command); + } + } + + /** + * @Then /(STDOUT|STDERR) should return exactly/ + */ + public function stdoutShouldReturnExactly($output_stream, PyStringNode $expected_output) + { + $command_output = $output_stream === "STDOUT" ? $this->command->stdout : $this->command->stderr; + + $command_output = str_replace(["\033[1;31m","\033[0m"],'',$command_output); + + $expected_out = isset($expected_output->getStrings()[0]) ? $expected_output->getStrings()[0] : ''; + if ( $expected_out !== trim($command_output)) { + throw new Exception("Actual output is:\n" . $command_output); + } + } + + /** + * @Then /(STDOUT|STDERR) should return something like/ + */ + public function stdoutShouldReturnSomethingLike($output_stream, PyStringNode $expected_output) + { + $command_output = $output_stream === "STDOUT" ? $this->command->stdout : $this->command->stderr; + + $expected_out = isset($expected_output->getStrings()[0]) ? $expected_output->getStrings()[0] : ''; + if (strpos($command_output, $expected_out) === false) { + throw new Exception("Actual output is:\n" . $command_output); + } + } + + /** + * @Then ee should be deleted + */ + public function eeShouldBeDeleted() + { + $result = EE::launch("docker ps -aqf label=org.label-schema.vendor=\"EasyEngine\" | wc -l", false, true); + if( trim($result->stdout) !== '0' ) { + throw new Exception("All containers have not been removed."); + } + $home = getenv('HOME'); + if(file_exists("$home/.ee/")){ + throw new Exception("~/.ee/ has not been removed"); + } + if(file_exists("$home/ee-sites/")){ + throw new Exception("~/ee-sites/ has not been removed"); + } + if(file_exists('/opt/easyengine/')){ + throw new Exception("/opt/easyengine/ has not been removed"); + } + } + + /** + * @AfterFeature + */ + public static function cleanup(AfterFeatureScope $scope) + { + if(file_exists('ee.phar')) { + unlink('ee.phar'); + } + if(file_exists('ee-old.phar')) { + unlink('ee-old.phar'); + } + } +} diff --git a/features/bootstrap/support.php b/features/bootstrap/support.php new file mode 100644 index 000000000..ed9a5b565 --- /dev/null +++ b/features/bootstrap/support.php @@ -0,0 +1,207 @@ + $value ) { + if ( ! compareContents( $value, $actual->$name ) ) { + return false; + } + } + } else if ( is_array( $expected ) ) { + foreach ( $expected as $key => $value ) { + if ( ! compareContents( $value, $actual[$key] ) ) { + return false; + } + } + } else { + return $expected === $actual; + } + + return true; +} + +/** + * Compare two strings containing JSON to ensure that @a $actualJson contains at + * least what the JSON string @a $expectedJson contains. + * + * @return whether or not @a $actualJson contains @a $expectedJson + * @retval true @a $actualJson contains @a $expectedJson + * @retval false @a $actualJson does not contain @a $expectedJson + * + * @param [in] $actualJson the JSON string to be tested + * @param [in] $expectedJson the expected JSON string + * + * Examples: + * expected: {'a':1,'array':[1,3,5]} + * + * 1 ) + * actual: {'a':1,'b':2,'c':3,'array':[1,2,3,4,5]} + * return: true + * + * 2 ) + * actual: {'b':2,'c':3,'array':[1,2,3,4,5]} + * return: false + * element 'a' is missing from the root object + * + * 3 ) + * actual: {'a':0,'b':2,'c':3,'array':[1,2,3,4,5]} + * return: false + * the value of element 'a' is not 1 + * + * 4 ) + * actual: {'a':1,'b':2,'c':3,'array':[1,2,4,5]} + * return: false + * the contents of 'array' does not include 3 + */ +function checkThatJsonStringContainsJsonString( $actualJson, $expectedJson ) { + $actualValue = json_decode( $actualJson ); + $expectedValue = json_decode( $expectedJson ); + + if ( ! $actualValue ) { + return false; + } + + return compareContents( $expectedValue, $actualValue ); +} + +/** + * Compare two strings to confirm $actualCSV contains $expectedCSV + * Both strings are expected to have headers for their CSVs. + * $actualCSV must match all data rows in $expectedCSV + * + * @param string A CSV string + * @param array A nested array of values + * + * @return bool Whether $actualCSV contains $expectedCSV + */ +function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) { + $actualCSV = array_map( 'str_getcsv', explode( PHP_EOL, $actualCSV ) ); + + if ( empty( $actualCSV ) ) { + return false; + } + + // Each sample must have headers + $actualHeaders = array_values( array_shift( $actualCSV ) ); + $expectedHeaders = array_values( array_shift( $expectedCSV ) ); + + // Each expectedCSV must exist somewhere in actualCSV in the proper column + $expectedResult = 0; + foreach ( $expectedCSV as $expected_row ) { + $expected_row = array_combine( $expectedHeaders, $expected_row ); + foreach ( $actualCSV as $actual_row ) { + + if ( count( $actualHeaders ) != count( $actual_row ) ) { + continue; + } + + $actual_row = array_intersect_key( array_combine( $actualHeaders, $actual_row ), $expected_row ); + if ( $actual_row == $expected_row ) { + $expectedResult ++; + } + } + } + + return $expectedResult >= count( $expectedCSV ); +} + +/** + * Compare two strings containing YAML to ensure that @a $actualYaml contains at + * least what the YAML string @a $expectedYaml contains. + * + * @return whether or not @a $actualYaml contains @a $expectedJson + * @retval true @a $actualYaml contains @a $expectedJson + * @retval false @a $actualYaml does not contain @a $expectedJson + * + * @param [in] $actualYaml the YAML string to be tested + * @param [in] $expectedYaml the expected YAML string + */ +function checkThatYamlStringContainsYamlString( $actualYaml, $expectedYaml ) { + $actualValue = Mustangostang\Spyc::YAMLLoad( $actualYaml ); + $expectedValue = Mustangostang\Spyc::YAMLLoad( $expectedYaml ); + + if ( ! $actualValue ) { + return false; + } + + return compareContents( $expectedValue, $actualValue ); +} + diff --git a/features/cli.feature b/features/cli.feature new file mode 100644 index 000000000..5223ec5d2 --- /dev/null +++ b/features/cli.feature @@ -0,0 +1,6 @@ +Feature: CLI Command + + Scenario: ee uninstall works properly + When I run 'sudo bin/ee cli info' + And I run 'sudo bin/ee cli self-uninstall --yes' + Then ee should be deleted diff --git a/img-versions.json b/img-versions.json new file mode 100644 index 000000000..99284685f --- /dev/null +++ b/img-versions.json @@ -0,0 +1,22 @@ +{ + "easyengine/cron": "v4.10.0", + "easyengine/mailhog": "v4.6.5", + "easyengine/mariadb": "v4.9.1", + "easyengine/nginx-proxy": "v4.10.0", + "easyengine/nginx": "v4.7.6", + "easyengine/php": "v4.6.6", + "easyengine/php5.6": "v4.7.4", + "easyengine/php7.0": "v4.7.4", + "easyengine/php7.2": "v4.7.4", + "easyengine/php7.3": "v4.7.4", + "easyengine/php7.4": "v4.7.4", + "easyengine/php8.0": "v4.8.1", + "easyengine/php8.1": "v4.9.0", + "easyengine/php8.2": "v4.9.1", + "easyengine/php8.3": "v4.10.0", + "easyengine/php8.4": "v4.10.0", + "easyengine/php8.5": "v4.10.0", + "easyengine/postfix": "v4.8.1", + "easyengine/redis": "v4.10.0", + "easyengine/newrelic-daemon": "v4.10.0" +} diff --git a/install b/install deleted file mode 100644 index 27ac955cf..000000000 --- a/install +++ /dev/null @@ -1,806 +0,0 @@ -#!/bin/bash - -# EasyEngine update script. -# This script is designed to install latest EasyEngine or -# to update current EasyEngine from 2.x to 3.x - -# Define echo function -# Blue color -function ee_lib_echo() -{ - echo $(tput setaf 4)$@$(tput sgr0) -} -# White color -function ee_lib_echo_info() -{ - echo $(tput setaf 7)$@$(tput sgr0) -} -# Red color -function ee_lib_echo_fail() -{ - echo $(tput setaf 1)$@$(tput sgr0) -} - -# Checking permissions -if [[ $EUID -ne 0 ]]; then - ee_lib_echo_fail "Sudo privilege required..." - ee_lib_echo_fail "Uses: wget -qO ee rt.cx/ee && sudo bash ee" - exit 100 -fi - -# Capture errors -function ee_lib_error() -{ - echo "[ `date` ] $(tput setaf 1)$@$(tput sgr0)" - exit $2 -} - -# Execute: apt-get update -ee_lib_echo "Executing apt-get update, please wait..." -apt-get update &>> /dev/null - -# Checking lsb_release package -if [ ! -x /usr/bin/lsb_release ]; then - ee_lib_echo "Installing lsb-release, please wait..." - apt-get -y install lsb-release &>> /dev/null -fi - -# Define variables for later use -ee_branch=$1 -readonly ee_version_old="2.2.3" -readonly ee_version_new="3.8.1" -readonly ee_log_dir=/var/log/ee/ -readonly ee_install_log=/var/log/ee/install.log -readonly ee_linux_distro=$(lsb_release -i | awk '{print $3}') -readonly ee_distro_version=$(lsb_release -sc) - -# Checking linux distro -if [ "$ee_linux_distro" != "Ubuntu" ] && [ "$ee_linux_distro" != "Debian" ]; then - ee_lib_echo_fail "EasyEngine (ee) is made for Ubuntu and Debian only as of now" - ee_lib_echo_fail "You are free to fork EasyEngine (ee): https://github.com/EasyEngine/easyengine/fork" - ee_lib_echo_fail "EasyEngine (ee) only support Ubuntu 14.04/16.04/18.04 and Debian 8.x" - exit 100 -fi - -# EasyEngine (ee) only support all Ubuntu/Debian distro except the distro reached EOL -lsb_release -d | egrep -e "14.04|16.04|18.04|jessie" &>> /dev/null -if [ "$?" -ne "0" ]; then - ee_lib_echo_fail "EasyEngine (ee) only support Ubuntu 14.04/16.04/18.04 LTS and Debian 8.x" - exit 100 -fi - -# Pre checks to avoid later screw ups -# Checking EasyEngine (ee) log directory -if [ ! -d $ee_log_dir ]; then - - ee_lib_echo "Creating EasyEngine log directory, please wait..." - mkdir -p $ee_log_dir || ee_lib_error "Unable to create log directory $ee_log_dir, exit status " $? - - # Create EasyEngine log files - touch /var/log/ee/{ee.log,install.log} - - # Keep EasyEngine log folder accessible to root only - chmod -R 700 /var/log/ee || ee_lib_error "Unable to change permissions for EasyEngine log folder, exit status " $? -fi - -# Install Python3, Git, Tar and python-software-properties required packages -# Generate Locale -function ee_install_dep() -{ - if [ "$ee_linux_distro" == "Ubuntu" ]; then - apt-get -y install build-essential curl gzip python3 python3-apt python3-setuptools python3-dev sqlite3 git tar software-properties-common || ee_lib_error "Unable to install pre depedencies, exit status " 1 - elif [ "$ee_linux_distro" == "Debian" ]; then - apt-get -y install build-essential curl gzip dirmngr python3 python3-apt python3-setuptools python3-dev sqlite3 git tar python-software-properties || ee_lib_error "Unable to pre depedencies, exit status " 1 - fi - - # Generating Locale - locale-gen en &>> /dev/null -} - -# Sqlite query to create table `sites` into ee.db -# which will be used by EasyEngine 3.x -function ee_sync_db() -{ - if [ ! -f /var/lib/ee/ee.db ]; then - mkdir -p /var/lib/ee - - echo "CREATE TABLE sites ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - sitename UNIQUE, - site_type CHAR, - cache_type CHAR, - site_path CHAR, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - is_enabled INT, - is_ssl INT, - storage_fs CHAR, - storage_db CHAR, - db_name VARCHAR, - db_user VARCHAR, - db_password VARCHAR, - db_host VARCHAR, - is_hhvm INT INT DEFAULT '0', - is_pagespeed INT INT DEFAULT '0', - php_version VARCHAR - );" | sqlite3 /var/lib/ee/ee.db - - # Check site is enable/live or disable - for site in $(ls /etc/nginx/sites-available/ | grep -v default); - do - if [ -f /etc/nginx/sites-enabled/$site ]; then - ee_site_status='1' - else - ee_site_status='0' - fi - - # Find out information about current NGINX configuration - ee_site_current_type=$(head -n1 /etc/nginx/sites-available/$site | grep "NGINX CONFIGURATION" | rev | cut -d' ' -f3,4,5,6,7 | rev | cut -d ' ' -f2,3,4,5) - - # Detect current website type and cache - if [ "$ee_site_current_type" = "HTML" ]; then - ee_site_current="html" - ee_site_current_cache="basic" - elif [ "$ee_site_current_type" = "PHP" ]; then - ee_site_current="php" - ee_site_current_cache="basic" - elif [ "$ee_site_current_type" = "MYSQL" ]; then - ee_site_current="mysql" - ee_site_current_cache="basic" - # Single WordPress - elif [ "$ee_site_current_type" = "WPSINGLE BASIC" ]; then - ee_site_current="wp" - ee_site_current_cache="basic" - - elif [ "$ee_site_current_type" = "WPSINGLE WP SUPER CACHE" ]; then - ee_site_current="wp" - ee_site_current_cache="wpsc" - - elif [ "$ee_site_current_type" = "WPSINGLE W3 TOTAL CACHE" ]; then - ee_site_current="wp" - ee_site_current_cache="w3tc" - - elif [ "$ee_site_current_type" = "WPSINGLE FAST CGI" ] || [ "$ee_site_current_type" = "WPSINGLE FASTCGI" ]; then - ee_site_current="wp" - ee_site_current_cache="wpfc" - - # WordPress subdirectory - elif [ "$ee_site_current_type" = "WPSUBDIR BASIC" ]; then - ee_site_current="wpsubdir" - ee_site_current_cache="basic" - - elif [ "$ee_site_current_type" = "WPSUBDIR WP SUPER CACHE" ]; then - ee_site_current="wpsubdir" - ee_site_current_cache="wpsc" - - elif [ "$ee_site_current_type" = "WPSUBDIR W3 TOTAL CACHE" ]; then - ee_site_current="wpsubdir" - ee_site_current_cache="w3tc" - - elif [ "$ee_site_current_type" = "WPSUBDIR FAST CGI" ] || [ "$ee_site_current_type" = "WPSUBDIR FASTCGI" ]; then - ee_site_current="wpsubdir" - ee_site_current_cache="wpfc" - - # WordPress subdomain - elif [ "$ee_site_current_type" = "WPSUBDOMAIN BASIC" ]; then - ee_site_current="wpsubdomain" - ee_site_current_cache="basic" - - elif [ "$ee_site_current_type" = "WPSUBDOMAIN WP SUPER CACHE" ]; then - ee_site_current="wpsubdomain" - ee_site_current_cache="wpsc" - - elif [ "$ee_site_current_type" = "WPSUBDOMAIN W3 TOTAL CACHE" ]; then - ee_site_current="wpsubdomain" - ee_site_current_cache="w3tc" - - elif [ "$ee_site_current_type" = "WPSUBDOMAIN FAST CGI" ] || [ "$ee_site_current_type" = "WPSUBDOMAIN FASTCGI" ]; then - ee_site_current="wpsubdomain" - ee_site_current_cache="wpfc" - fi - - ee_webroot="/var/www/$site" - - # Insert query to insert old site information into ee.db - echo "INSERT INTO sites (sitename, site_type, cache_type, site_path, is_enabled, is_ssl, storage_fs, storage_db) - VALUES (\"$site\", \"$ee_site_current\", \"$ee_site_current_cache\", \"$ee_webroot\", \"$ee_site_status\", 0, 'ext4', 'mysql');" | sqlite3 /var/lib/ee/ee.db - - done - else - ee_php_version=$(php -v | head -n1 | cut -d' ' -f2 |cut -c1-3) - ee_lib_echo "Updating EasyEngine Database" - echo "ALTER TABLE sites ADD COLUMN db_name varchar;" | sqlite3 /var/lib/ee/ee.db - echo "ALTER TABLE sites ADD COLUMN db_user varchar; " | sqlite3 /var/lib/ee/ee.db - echo "ALTER TABLE sites ADD COLUMN db_password varchar;" | sqlite3 /var/lib/ee/ee.db - echo "ALTER TABLE sites ADD COLUMN db_host varchar;" | sqlite3 /var/lib/ee/ee.db - echo "ALTER TABLE sites ADD COLUMN is_hhvm INT DEFAULT '0';" | sqlite3 /var/lib/ee/ee.db - echo "ALTER TABLE sites ADD COLUMN is_pagespeed INT DEFAULT '0';" | sqlite3 /var/lib/ee/ee.db - echo "ALTER TABLE sites ADD COLUMN php_version varchar DEFAULT \"$ee_php_version\";" | sqlite3 /var/lib/ee/ee.db - fi -} - - -function secure_ee_db() -{ - chown -R root:root /var/lib/ee/ - chmod -R 600 /var/lib/ee/ -} - -function ee_update_wp_cli() -{ - ee_lib_echo "Updating WP-CLI version to resolve compatibility issue." - PHP_PATH=$(which php) - WP_CLI_PATH=$(which wp) - if [ "${WP_CLI_PATH}" != "" ]; then - # Get WP-CLI version - WP_CLI_VERSION=$(${PHP_PATH} ${WP_CLI_PATH} --allow-root cli version | awk '{ print $2 }') - dpkg --compare-versions ${WP_CLI_VERSION} lt 1.4.1 - # Update WP-CLI version - if [ "$?" == "0" ]; then - wget -qO ${WP_CLI_PATH} https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar - chmod +x ${WP_CLI_PATH} - fi - fi -} - -function check_pagespeed() -{ - ee_lib_echo_info "Please Note: We have removed pagespeed from current version of our nginx build." - ee_lib_echo_info "Verifiying if PageSpeed is not used..." - - if [ -f /etc/nginx/conf.d/pagespeed.conf ] - then - PAGESPEEDSITE="$(echo "SELECT sitename FROM sites WHERE is_pagespeed=1;"| sqlite3 /var/lib/ee/ee.db | wc -l)" - - if [ ${PAGESPEEDSITE} -ge 0 ]; then - ee_lib_echo_fail "Issue: Update script has found PageSpeed on following site" - echo "SELECT sitename FROM sites WHERE is_pagespeed=1;"| sqlite3 /var/lib/ee/ee.db - ee_lib_echo_fail "Please remove Pagespeed from above site using:" - ee_lib_echo_fail "ee site update example.com --pagespeed=off or follow this blog: https://easyengine.io/blog/disabling-pagespeed/ " - ee_lib_error "Once done, run `ee update` again" - fi - else - ee_lib_echo_info "OK: PageSpeed not Present." - fi - -} - -# Install EasyEngine 3.x -function ee_install() -{ - # Remove old clone of EasyEngine (ee) if any - rm -rf /tmp/easyengine &>> /dev/null - - # Clone EE 3.0 Python ee_branch - ee_lib_echo "Cloning EasyEngine, please wait..." - if [ "$ee_branch" = "" ]; then - ee_branch=master - fi - - git clone -b $ee_branch https://github.com/EasyEngine/easyengine.git /tmp/easyengine --quiet > /dev/null \ - || ee_lib_error "Unable to clone EasyEngine, exit status" $? - - cd /tmp/easyengine - ee_lib_echo "Installing EasyEngine, please wait..." - python3 setup.py install || ee_lib_error "Unable to install EasyEngine, exit status " $? -} - -# Update EasyEngine configuration -# Remove EasyEngine 2.x -function ee_update() -{ - # Preserve old configuration - ee_lib_echo "Updating EasyEngine configuration, please wait..." - - ee_grant_host=$(grep grant-host /etc/easyengine/ee.conf | awk '{ print $3 }' | head -1 ) - ee_db_name=$(grep db-name /etc/easyengine/ee.conf | awk '{ print $3 }') - ee_db_user=$(grep db-name /etc/easyengine/ee.conf | awk '{ print $3 }') - ee_wp_prefix=$(grep prefix /etc/easyengine/ee.conf | awk '{ print $3 }') - ee_wp_user=$(grep 'user ' /etc/easyengine/ee.conf | grep -v db-user |awk '{ print $3 }') - ee_wp_pass=$(grep password /etc/easyengine/ee.conf | awk '{ print $3 }') - ee_wp_email=$(grep email /etc/easyengine/ee.conf | awk '{ print $3 }') - ee_ip_addr=$(grep ip-address /etc/easyengine/ee.conf |awk -F'=' '{ print $2 }') - - sed -i "s/ip-address.*/ip-address = ${ee_ip_addr}/" /etc/ee/ee.conf && \ - sed -i "s/grant-host.*/grant-host = ${ee_grant_host}/" /etc/ee/ee.conf && \ - sed -i "s/db-name.*/db-name = ${db-name}/" /etc/ee/ee.conf && \ - sed -i "s/db-user.*/db-user = ${ee_db_user}/" /etc/ee/ee.conf && \ - sed -i "s/prefix.*/prefix = ${ee_wp_prefix}/" /etc/ee/ee.conf && \ - sed -i "s/^user.*/user = ${ee_wp_user}/" /etc/ee/ee.conf && \ - sed -i "s/password.*/password = ${ee_wp_password}/" /etc/ee/ee.conf && \ - sed -i "s/email.*/email = ${ee_wp_email}/" /etc/ee/ee.conf || ee_lib_error "Unable to update configuration, exit status " $? - - # Remove old EasyEngine - ee_lib_echo "Removing EasyEngine 2.x" - rm -rf /etc/bash_completion.d/ee /etc/easyengine/ /usr/share/easyengine/ /usr/local/lib/easyengine /usr/local/sbin/easyengine /usr/local/sbin/ee /var/log/easyengine - - # Softlink to fix command not found error - ln -s /usr/local/bin/ee /usr/local/sbin/ee || ee_lib_error "Unable to create softlink to old EasyEngine, exit status " $? -} - -function ee_upgrade_php(){ - #Upgrade PHP5.6 to a new repo supporting PHP 7.0 - if [ "$ee_distro_version" == "trusty" ]; then - if [ -f /etc/apt/sources.list.d/ondrej-php5-5_6-trusty.list ]; then - # add-apt-repository -y --remove 'ppa:ondrej/php5-5.6' - add-apt-repository -y 'ppa:ondrej/php' - ee_lib_echo "Upgrading required packages, please wait..." - apt-get update &>> /dev/null - apt-get -y install php5.6-fpm php5.6-curl php5.6-gd php5.6-imap php5.6-mcrypt php5.6-readline php5.6-mysql php5.6-cli php5.6-common php5.6-curl php5.6-mbstring php5.6-bcmath php5.6-recode php5.6-mysql php5.6-opcache php-memcached php-imagick memcached graphviz php-pear php-xdebug php-msgpack php5.6-zip php5.6-xml php5.6-soap php-memcache || ee_lib_error "Unable to install PHP 5.6 packages, exit status " 1 - if [ -e /etc/php5/fpm/pool.d/www.conf -a -e /etc/php5/fpm/pool.d/debug.conf -a -e /etc/php5/fpm/php.ini -a -e /etc/php5/fpm/php-fpm.conf ]; then - cp -f /etc/php5/fpm/pool.d/www.conf /etc/php/5.6/fpm/pool.d/www.conf &>> /dev/null - cp -f /etc/php5/fpm/pool.d/debug.conf /etc/php/5.6/fpm/pool.d/debug.conf &>> /dev/null - cp -f /etc/php5/fpm/php.ini /etc/php/5.6/fpm/php.ini &>> /dev/null - cp -f /etc/php5/fpm/php-fpm.conf /etc/php/5.6/fpm/php-fpm.conf &>> /dev/null - else - echo "Some files are missing." || ee_lib_error "Unable to configure PHP5.6 packages, exit status " 1 - fi - sed -i "s/pid.*/pid = \/run\/php\/php5.6-fpm.pid/" /etc/php/5.6/fpm/php-fpm.conf && \ - sed -i "s/error_log.*/error_log = \/var\/log\/php\/5.6\/fpm.log/" /etc/php/5.6/fpm/php-fpm.conf && \ - sed -i "s/log_level.*/log_level = notice/" /etc/php/5.6/fpm/php-fpm.conf && \ - sed -i "s/include.*/include = \/etc\/php\/5.6\/fpm\/pool.d\/*.conf/" /etc/php/5.6/fpm/php-fpm.conf && \ - sed -i "s/slowlog =.*/slowlog = \/var\/log\/php\/5.6\/slow.log/" /etc/php/5.6/fpm/pool.d/debug.conf || ee_lib_error "Unable to update configuration, exit status " $? - mkdir -p /var/log/php/5.6/ - touch /var/log/php/5.6/slow.log /var/log/php/5.6/fpm.log - service php5-fpm stop &>> /dev/null - service php5.6-fpm restart &>> /dev/null - rm -f /etc/apt/sources.list.d/ondrej-php5-5_6-trusty.list &>> /dev/null - apt-get remove -y php5-fpm php5-curl php5-gd php5-imap php5-mcrypt php5-common php5-readline php5-mysql php5-cli php5-memcache php5-imagick memcached graphviz php-pear - - #Fix for PHP 5.6 + 7.0 missed packages - elif [ -f /etc/php/mods-available/readline.ini ]; then - mkdir -p /tmp/php-conf/5.6 - mkdir -p /tmp/php-conf/7.0 - cp -f /etc/php/5.6/fpm/pool.d/www.conf /tmp/php-conf/5.6 &>> /dev/null - cp -f /etc/php/5.6/fpm/pool.d/debug.conf /tmp/php-conf/5.6 &>> /dev/null - cp -f /etc/php/5.6/fpm/php.ini /tmp/php-conf/5.6 &>> /dev/null - cp -f /etc/php/5.6/fpm/php-fpm.conf /tmp/php-conf/5.6 &>> /dev/null - - cp -f /etc/php/7.0/fpm/pool.d/www.conf /tmp/php-conf/7.0 &>> /dev/null - cp -f /etc/php/7.0/fpm/pool.d/debug.conf /tmp/php-conf/7.0 &>> /dev/null - cp -f /etc/php/7.0/fpm/php.ini /tmp/php-conf/7.0 &>> /dev/null - cp -f /etc/php/7.0/fpm/php-fpm.conf /tmp/php-conf/7.0 &>> /dev/null - - - apt-get -y install php5.6-fpm php5.6-curl php5.6-gd php5.6-imap php5.6-mcrypt php5.6-readline php5.6-mysql php5.6-cli php5.6-common php5.6-curl php5.6-mbstring php5.6-bcmath php5.6-recode php5.6-mysql php5.6-opcache php-memcached php-imagick memcached graphviz php-pear php-xdebug php-msgpack php5.6-zip php5.6-xml php-memcache || ee_lib_error "Unable to install PHP 5.6 packages, exit status " 1 - dpkg-query -W -f='${Status} ${Version}\n' php7.0-fpm 2>/dev/null | grep installed - if [ "$?" -eq "0" ]; then - apt-get -y install php7.0-fpm php7.0-curl php7.0-gd php7.0-imap php7.0-mcrypt php7.0-readline php7.0-common php7.0-recode php7.0-mysql php7.0-cli php7.0-curl php7.0-mbstring php7.0-bcmath php7.0-mysql php7.0-opcache php7.0-zip php7.0-xml php-memcached php-imagick php-memcache memcached graphviz php-pear php-xdebug php-msgpack php7.0-soap || ee_lib_error "Unable to install PHP 7.0 packages, exit status " 1 - mv -f /tmp/php-conf/7.0/www.conf /etc/php/7.0/fpm/pool.d/www.conf &>> /dev/null - mv -f /tmp/php-conf/7.0/debug.conf /etc/php/7.0/fpm/pool.d/debug.conf &>> /dev/null - mv -f /tmp/php-conf/7.0/php.ini /etc/php/7.0/fpm/php.ini &>> /dev/null - mv -f /tmp/php-conf/7.0/php-fpm.conf /etc/php/7.0/fpm/php-fpm.conf &>> /dev/null - service php7.0-fpm restart &>> /dev/null - fi - - mv -f /tmp/php-conf/5.6/www.conf /etc/php/5.6/fpm/pool.d/www.conf &>> /dev/null - mv -f /tmp/php-conf/5.6/debug.conf /etc/php/5.6/fpm/pool.d/debug.conf &>> /dev/null - mv -f /tmp/php-conf/5.6/php.ini /etc/php/5.6/fpm/php.ini &>> /dev/null - mv -f /tmp/php-conf/5.6/php-fpm.conf /etc/php/5.6/fpm/php-fpm.conf &>> /dev/null - - service php5.6-fpm restart &>> /dev/null - rm -rf /tmp/php-conf - fi - fi - -} - -function ee_update_latest() -{ - -if [ -f /etc/nginx/fastcgi_params ] -then - cat /etc/nginx/fastcgi_params| grep -q 'HTTP_PROXY' - if [[ $? -ne 0 ]]; then - echo "fastcgi_param HTTP_PROXY \"\";" >> /etc/nginx/fastcgi_params - echo "fastcgi_param HTTP_PROXY \"\";" >> /etc/nginx/fastcgi.conf - service nginx restart &>> /dev/null - fi -fi - - - -if [ -f /etc/ImageMagick/policy.xml ] - then - if [ ! -f /etc/ImageMagick/patch.txt ] - then - echo -e "\t\n\t\n\t\n\t\n\t" >> /etc/ImageMagick/patch.txt - sed -i '//r /etc/ImageMagick/patch.txt' /etc/ImageMagick/policy.xml - fi - fi - - #Move ~/.my.cnf to /etc/mysql/conf.d/my.cnf - if [ ! -f /etc/mysql/conf.d/my.cnf ] - then - #create conf.d folder if not exist - if [ ! -d /etc/mysql/conf.d ]; then - mkdir -p /etc/mysql/conf.d - chmod 755 /etc/mysql/conf.d - fi - if [ -d /etc/mysql/conf.d ] - then - if [ -f ~/.my.cnf ] - then - cp ~/.my.cnf /etc/mysql/conf.d/my.cnf &>> /dev/null - chmod 600 /etc/mysql/conf.d/my.cnf - else - if [ -f /root/.my.cnf ] - then - cp /root/.my.cnf /etc/mysql/conf.d/my.cnf &>> /dev/null - chmod 600 /etc/mysql/conf.d/my.cnf - else - ee_lib_echo_fail ".my.cnf cannot be located in your current user or root." - fi - fi - fi - fi - - - if [ -f /etc/nginx/nginx.conf ]; then - ee_lib_echo "Updating Nginx configuration, please wait..." - # From version 3.1.10 we are using Suse builder for repository - if [ "$ee_distro_version" == "precise" ]; then - grep -Hr 'http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_12.04/ /' /etc/apt/sources.list.d/ &>> /dev/null - if [[ $? -ne 0 ]]; then - if [ -f /etc/apt/sources.list.d/rtcamp-nginx-precise.list ]; then - rm -rf /etc/apt/sources.list.d/rtcamp-nginx-precise.list - fi - echo -e "\ndeb http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_12.04/ /" >> /etc/apt/sources.list.d/ee-repo.list - gpg --keyserver "hkp://pgp.mit.edu" --recv-keys '3050AC3CD2AE6F03' - gpg -a --export --armor '3050AC3CD2AE6F03' | apt-key add - - if [ -f /etc/nginx/conf.d/ee-nginx.conf ]; then - mv /etc/nginx/conf.d/ee-nginx.conf /etc/nginx/conf.d/ee-nginx.conf.old &>> /dev/null - fi - mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old &>> /dev/null - apt-get update - service nginx stop &>> /dev/null - DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-ee openssl - service nginx restart &>> /dev/null - fi - dpkg --get-selections | grep -v deinstall | grep nginx-common - if [ $? -eq 0 ]; then - apt-get update - dpkg --get-selections | grep -v deinstall | grep nginx-mainline - if [ $? -eq 0 ]; then - apt-get remove -y nginx-mainline - fi - service nginx stop &>> /dev/null - DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y --allow-unauthenticated install nginx-ee nginx-custom - service nginx restart &>> /dev/null - fi - dpkg --get-selections | grep -v deinstall | grep nginx-mainline - - elif [ "$ee_distro_version" == "trusty" ]; then - grep -Hr 'http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_14.04/ /' /etc/apt/sources.list.d/ &>> /dev/null - if [[ $? -ne 0 ]]; then - if [ -f /etc/apt/sources.list.d/rtcamp-nginx-trusty.list ]; then - rm -rf /etc/apt/sources.list.d/rtcamp-nginx-trusty.list - fi - echo -e "\ndeb http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/xUbuntu_14.04/ /" >> /etc/apt/sources.list.d/ee-repo.list - gpg --keyserver "hkp://pgp.mit.edu" --recv-keys '3050AC3CD2AE6F03' - gpg -a --export --armor '3050AC3CD2AE6F03' | apt-key add - - if [ -f /etc/nginx/conf.d/ee-nginx.conf ]; then - mv /etc/nginx/conf.d/ee-nginx.conf /etc/nginx/conf.d/ee-nginx.conf.old &>> /dev/null - fi - mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old &>> /dev/null - apt-get update - service nginx stop &>> /dev/null - DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-custom nginx-ee - service nginx restart &>> /dev/null - fi - dpkg --get-selections | grep -v deinstall | grep nginx-common - if [ $? -eq 0 ]; then - apt-get update - dpkg --get-selections | grep -v deinstall | grep nginx-mainline - if [ $? -eq 0 ]; then - apt-get remove -y nginx-mainline - fi - service nginx stop &>> /dev/null - DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y --allow-unauthenticated install nginx-ee nginx-custom - service nginx restart &>> /dev/null - fi - - elif [ "$ee_distro_version" == "wheezy" ]; then - grep -Hr 'http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_7.0/ /' /etc/apt/sources.list.d/ &>> /dev/null - #grep -Hr "deb http://packages.dotdeb.org wheezy all" /etc/apt/sources.list.d/ee-repo.list &>> /dev/null - if [[ $? -ne 0 ]]; then - # if [ -f /etc/apt/sources.list.d/dotdeb-wheezy.list ]; then - # rm -rf /etc/apt/sources.list.d/dotdeb-wheezy.list - # else - # sed -i "/deb http:\/\/packages.dotdeb.org wheezy all/d" /etc/apt/sources.list.d/ee-repo.list &>> /dev/null - # fi - echo -e "deb http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_7.0/ /" >> /etc/apt/sources.list.d/ee-repo.list - gpg --keyserver "hkp://pgp.mit.edu" --recv-keys '3050AC3CD2AE6F03' - gpg -a --export --armor '3050AC3CD2AE6F03' | apt-key add - - if [ -f /etc/nginx/conf.d/ee-nginx.conf ]; then - mv /etc/nginx/conf.d/ee-nginx.conf /etc/nginx/conf.d/ee-nginx.conf.old &>> /dev/null - fi - mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old &>> /dev/null - mv /etc/nginx/fastcgi_params /etc/nginx/fastcgi_params.old &>> /dev/null - apt-get update - service nginx stop &>> /dev/null - DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-custom - service nginx restart &>> /dev/null - fi - dpkg --get-selections | grep -v deinstall | grep nginx-common - if [ $? -eq 0 ]; then - apt-get update - service nginx stop &>> /dev/null - DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-ee nginx-custom - service nginx restart &>> /dev/null - fi - elif [ "$ee_distro_version" == "jessie" ]; then - - grep -Hr 'http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_8.0/ /' /etc/apt/sources.list.d/ &>> /dev/null - #grep -Hr "deb http://packages.dotdeb.org jessie all" /etc/apt/sources.list.d/ee-repo.list &>> /dev/null - if [[ $? -ne 0 ]]; then - #sed -i "/deb http:\/\/packages.dotdeb.org jessie all/d" /etc/apt/sources.list.d/ee-repo.list &>> /dev/null - echo -e "deb http://download.opensuse.org/repositories/home:/rtCamp:/EasyEngine/Debian_8.0/ /" >> /etc/apt/sources.list.d/ee-repo.list - gpg --keyserver "hkp://pgp.mit.edu" --recv-keys '3050AC3CD2AE6F03' - gpg -a --export --armor '3050AC3CD2AE6F03' | apt-key add - - if [ -f /etc/nginx/conf.d/ee-nginx.conf ]; then - mv /etc/nginx/conf.d/ee-nginx.conf /etc/nginx/conf.d/ee-nginx.conf.old &>> /dev/null - fi - mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old &>> /dev/null - mv /etc/nginx/fastcgi_params /etc/nginx/fastcgi_params.old &>> /dev/null - apt-get update - service nginx stop &>> /dev/null - apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y install nginx-custom - service nginx restart &>> /dev/null - fi - dpkg --get-selections | grep -v deinstall | grep nginx-common - if [ $? -eq 0 ]; then - apt-get update - dpkg --get-selections | grep -v deinstall | grep nginx-mainline - if [ $? -eq 0 ]; then - apt-get remove -y nginx-mainline - fi - service nginx stop &>> /dev/null - DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confmiss" -o Dpkg::Options::="--force-confold" -y --allow-unauthenticated install nginx-ee nginx-custom - service nginx restart &>> /dev/null - fi - fi - fi - - if [ -f /etc/nginx/nginx.conf ]; then - sed -i "s/.*X-Powered-By.*/\tadd_header X-Powered-By \"EasyEngine $ee_version_new\";/" /etc/nginx/nginx.conf &>> /dev/null - fi - - if [ -f /etc/nginx/conf.d/ee-plus.conf ]; then - sed -i "s/.*X-Powered-By.*/\tadd_header X-Powered-By \"EasyEngine $ee_version_new\";/" /etc/nginx/conf.d/ee-plus.conf &>> /dev/null - fi - - # Disable Xdebug on old systems if and only if ee debug is off - if [ -f /etc/php5/mods-available/xdebug.ini ]; then - ee_debug_value=$(grep -Hr 9001 /etc/nginx/conf.d/upstream.conf | wc -l ) - if [ $ee_debug_value -eq 1 ]; then - grep -Hr ";zend_extension" /etc/php5/mods-available/xdebug.ini &>> /dev/null - if [ $? -ne 0 ]; then - sed -i "s/zend_extension/;zend_extension/" /etc/php5/mods-available/xdebug.ini - fi - fi - fi - - # Fix HHVM autostart on reboot - dpkg --get-selections | grep -v deinstall | grep hhvm &>> /dev/null - if [ $? -eq 0 ]; then - update-rc.d hhvm defaults &>> /dev/null - fi - - # Fix WordPress example.html issue - # Ref: http://wptavern.com/xss-vulnerability-in-jetpack-and-the-twenty-fifteen-default-theme-affects-millions-of-wordpress-users - dpkg --get-selections | grep -v deinstall | grep nginx &>> /dev/null - if [ $? -eq 0 ]; then - cp /usr/lib/ee/templates/locations.mustache /etc/nginx/common/locations.conf &>> /dev/null - fi - - # Fix HHVM upstream issue that was preventing from using EasyEngine for site operations - if [ -f /etc/nginx/conf.d/upstream.conf ]; then - grep -Hr hhvm /etc/nginx/conf.d/upstream.conf &>> /dev/null - if [ $? -ne 0 ]; then - echo -e "upstream hhvm {\n# HHVM Pool\nserver 127.0.0.1:8000;\nserver 127.0.0.1:9000 backup;\n}\n" >> /etc/nginx/conf.d/upstream.conf - fi - fi - - # Fix HHVM server IP - if [ -f /etc/hhvm/server.ini ]; then - grep -Hr "hhvm.server.ip" /etc/hhvm/server.ini &>> /dev/null - if [ $? -ne 0 ]; then - echo -e "hhvm.server.ip = 127.0.0.1\n" >> /etc/hhvm/server.ini - fi - fi - - - # Rename Redis Header - if [ -f /etc/nginx/common/redis-hhvm.conf ]; then - sed -i "s/X-Cache /X-SRCache-Fetch-Status /g" /etc/nginx/common/redis-hhvm.conf &>> /dev/null - sed -i "s/X-Cache-2 /X-SRCache-Store-Status /g" /etc/nginx/common/redis-hhvm.conf &>> /dev/null - fi - - if [ -f /etc/nginx/common/redis.conf ]; then - sed -i "s/X-Cache /X-SRCache-Fetch-Status /g" /etc/nginx/common/redis.conf &>> /dev/null - sed -i "s/X-Cache-2 /X-SRCache-Store-Status /g" /etc/nginx/common/redis.conf &>> /dev/null - fi - - - if [ -f /etc/nginx/common/redis-hhvm.conf ]; then - # Update Timeout redis-hhvm.conf - grep -0 'redis2_query expire $key 6h' /etc/nginx/common/redis-hhvm.conf &>> /dev/null - if [ $? -eq 0 ]; then - sed -i 's/redis2_query expire $key 6h/redis2_query expire $key 14400/g' /etc/nginx/common/redis-hhvm.conf &>> /dev/null - fi - - #Fix for 3.3.4 redis-hhvm issue - grep -0 'HTTP_ACCEPT_ENCODING' /etc/nginx/common/redis-hhvm.conf &>> /dev/null - if [ $? -ne 0 ]; then - sed -i 's/fastcgi_params;/fastcgi_params;\n fastcgi_param HTTP_ACCEPT_ENCODING "";/g' /etc/nginx/common/redis-hhvm.conf &>> /dev/null - fi - fi - - #Fix Security Issue. commit #c64f28e - if [ -f /etc/nginx/common/locations.conf ]; then - grep -0 '$request_uri ~\* \"^.+(readme|license|example)\\.(txt|html)$\"' /etc/nginx/common/locations.conf &>> /dev/null - if [ $? -eq 0 ]; then - sed -i 's/$request_uri ~\* \"^.+(readme|license|example)\\.(txt|html)$\"/$uri ~\* \"^.+(readme|license|example)\\.(txt|html)$\"/g' /etc/nginx/common/locations.conf &>> /dev/null - fi - fi - - #Fix Redis-server security issue - #http://redis.io/topics/security - if [ -f /etc/redis/redis.conf ]; then - grep -0 -v "#" /etc/redis/redis.conf | grep 'bind' &>> /dev/null - if [ $? -ne 0 ]; then - sed -i '$ a bind 127.0.0.1' /etc/redis/redis.conf &>> /dev/null - service redis-server restart &>> /dev/null - fi - fi - - #Fix For --letsencrypt - if [ -f /etc/nginx/common/locations.conf ]; then - grep -0 'location ~ \/\\.well-known' /etc/nginx/common/locations.conf &>> /dev/null - if [ $? -ne 0 ]; then - sed -i 's/# Deny hidden files/# Deny hidden files\nlocation ~ \/\\.well-known {\n allow all;\n}\n /g' /etc/nginx/common/locations.conf &>> /dev/null - fi - fi - - # Fix for 3.3.2 renamed nginx.conf - nginx -V 2>&1 &>>/dev/null - if [[ $? -eq 0 ]]; then - nginx -t 2>&1 | grep 'open() "/etc/nginx/nginx.conf" failed' &>>/dev/null - if [[ $? -eq 0 ]]; then - if [ -f /etc/nginx/nginx.conf.old ]; then - if [ ! -f /etc/nginx/nginx.conf ]; then - cp /etc/nginx/nginx.conf.old /etc/nginx/nginx.conf - fi - fi - fi - # Fix for 3.3.2 renamed fastcgi_param - nginx -t 2>&1 | grep 'open() "/etc/nginx/fastcgi_params" failed' &>>/dev/null - if [[ $? -eq 0 ]]; then - if [ -f /etc/nginx/fastcgi_params.old ]; then - if [ ! -f /etc/nginx/fastcgi_params ]; then - cp /etc/nginx/fastcgi_params.old /etc/nginx/fastcgi_params - fi - fi - fi - fi - - #Fix For ssl_ciphers - if [ -f /etc/nginx/nginx.conf ]; then - sed -i 's/HIGH:!aNULL:!MD5:!kEDH;/ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;/' /etc/nginx/nginx.conf - fi - - #Fix for SSL cert --all - crontab -l | grep -q '\-\-min_expiry_limit' - if [[ $? -eq 0 ]]; then - crontab -l > /var/spool/cron/cron-backup.txt #backup cron before editing - crontab -l | sed '/--min_expiry_limit/d' | crontab - - /bin/bash -c "crontab -l 2> /dev/null | { cat; echo -e \"\n0 0 * * 0 ee site update --le=renew --all 2> /dev/null # Renew all letsencrypt SSL cert. Set by EasyEngine\"; } | crontab -" - fi - - - -} - -# Do git intialisation -function ee_git_init() -{ - # Nginx under git version control - if [ -d /etc/nginx ];then - cd /etc/nginx - if [ ! -d /etc/nginx/.git ]; then - git init &>> /dev/null - fi - git add -A . - git commit -am "Updated Nginx" > /dev/null - fi - # EasyEngine under git version control - cd /etc/ee - if [ ! -d /etc/ee/.git ]; then - git init > /dev/null - fi - git add -A . - git commit -am "Installed/Updated to EasyEngine 3.x" &>> /dev/null - - #PHP under git version control - if [ -d /etc/php ];then - cd /etc/php - if [ ! -d /etc/php/.git ]; then - git init &>> /dev/null - fi - git add -A . - git commit -am "Updated PHP" > /dev/null - fi - -} - -# Update EasyEngine -if [ -f /usr/local/sbin/easyengine ]; then - # Check old EasyEngine version - ee version | grep ${ee_version_old} &>> /dev/null - if [[ $? -ne 0 ]]; then - ee_lib_echo "EasyEngine $ee_version_old not found on your system" | tee -ai $ee_install_log - ee_lib_echo "Updating your EasyEngine to $ee_version_old for compability" | tee -ai $ee_install_log - wget -q https://raw.githubusercontent.com/EasyEngine/easyengine/old-stable/bin/update && bash update - if [[ $? -ne 0 ]]; then - ee_lib_echo_fail "Unable to update EasyEngine to $ee_version_old, exit status = " $? - exit 100 - fi - fi - read -p "Update EasyEngine to $ee_version_new (y/n): " ee_ans - if [ "$ee_ans" = "y" ] || [ "$ee_ans" = "Y" ]; then - check_pagespeed | tee -ai $ee_install_log - ee_install_dep | tee -ai $ee_install_log - ee_sync_db 2&>>1 $EE_INSTALL_LOG - secure_ee_db | tee -ai $EE_INSTALL_LOG - ee_upgrade_php | tee -ai $ee_install_log - ee_install | tee -ai $ee_install_log - ee_update | tee -ai $ee_install_log - ee_update_latest | tee -ai $ee_install_log - ee_git_init | tee -ai $ee_install_log - else - ee_lib_error "Not updating EasyEngine to $ee_version_new, exit status = " 1 - fi -elif [ ! -f /usr/local/bin/ee ]; then - ee_lib_echo "Installing depedencies" | tee -ai $ee_install_log - ee_install_dep | tee -ai $ee_install_log - ee_lib_echo "Installing EasyEngine $ee_branch" | tee -ai $ee_install_log - ee_install | tee -ai $ee_install_log - ee_lib_echo "Running post-install steps" | tee -ai $ee_install_log - secure_ee_db | tee -ai $EE_INSTALL_LOG - ee_git_init | tee -ai $ee_install_log - -else - ee -v 2>&1 | grep $ee_version_new &>> /dev/null - if [[ $? -ne 0 ]];then - read -p "Update EasyEngine to $ee_version_new (y/n): " ee_ans - if [ "$ee_ans" = "y" ] || [ "$ee_ans" = "Y" ]; then - ee_install_dep | tee -ai $ee_install_log - ee_sync_db 2&>>1 $EE_INSTALL_LOG - secure_ee_db | tee -ai $EE_INSTALL_LOG - ee_upgrade_php | tee -ai $ee_install_log - ee_install | tee -ai $ee_install_log - ee_update_latest | tee -ai $ee_install_log - ee_git_init | tee -ai $ee_install_log - service nginx reload &>> /dev/null - if [ "$ee_distro_version" == "trusty" ]; then - service php5.6-fpm restart &>> /dev/null - else - service php5-fpm restart &>> /dev/null - fi - ee_update_wp_cli | tee -ai $ee_install_log - else - ee_lib_error "Not updating EasyEngine to $ee_version_new, exit status = " 1 - fi - else - ee_lib_error "You already have EasyEngine $ee_version_new, exit status = " 1 - fi -fi -ee sync | tee -ai $EE_INSTALL_LOG - -echo -ee_lib_echo "For EasyEngine (ee) auto completion, run the following command" -echo -ee_lib_echo_info "source /etc/bash_completion.d/ee_auto.rc" -echo -ee_lib_echo "EasyEngine (ee) installed/updated successfully" -ee_lib_echo "EasyEngine (ee) help: http://docs.rtcamp.com/easyengine/" diff --git a/migrations/db/20181016052850_easyengine_insert_docker_images_version.php b/migrations/db/20181016052850_easyengine_insert_docker_images_version.php new file mode 100644 index 000000000..8fb651702 --- /dev/null +++ b/migrations/db/20181016052850_easyengine_insert_docker_images_version.php @@ -0,0 +1,110 @@ +setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION ); + } catch ( \PDOException $exception ) { + EE::error( $exception->getMessage() ); + } + + } + + /** + * Execute create table query for site and sitemeta table. + * + * @throws EE\ExitException + */ + public function up() { + + EE::log( 'Checking and Pulling required images. This may take some time.' ); + $images = EE\Utils\get_image_versions(); + + $query = ''; + $skip_download = [ + 'easyengine/php5.6', + 'easyengine/php7.0', + 'easyengine/php7.2', + 'easyengine/php7.3', + 'easyengine/php7.4', + 'easyengine/php', + 'easyengine/php8.0', + 'easyengine/php8.1', + 'easyengine/mailhog', + 'easyengine/newrelic-daemon', + ]; + + $pull_queue = []; + foreach ( $images as $image => $tag ) { + if ( in_array( $image, $skip_download ) ) { + continue; + } + $pull_queue[] = [ $image, $tag ]; + } + + $concurrency = 4; + $running = []; + $successful_pulls = []; + + while ( ! empty( $pull_queue ) || ! empty( $running ) ) { + // Start new processes if under concurrency limit + while ( count( $running ) < $concurrency && ! empty( $pull_queue ) ) { + list( $image, $tag ) = array_shift( $pull_queue ); + EE::log( "Checking and Pulling docker image $image:$tag" ); + $process = new Process( [ 'docker', 'pull', "$image:$tag" ] ); + $process->start(); + $running[] = [ $process, $image, $tag ]; + } + + // Check for finished processes + foreach ( $running as $key => list( $process, $image, $tag ) ) { + if ( ! $process->isRunning() ) { + if ( ! $process->isSuccessful() ) { + throw new \Exception( "Unable to pull $image:$tag: " . $process->getErrorOutput() ); + } + $successful_pulls[] = [ $image, $tag ]; + unset( $running[ $key ] ); + } + } + + usleep( 100000 ); // Sleep 100ms to avoid busy loop + } + + foreach ( $successful_pulls as list( $image, $tag ) ) { + $query .= "INSERT INTO options VALUES( '${image}', '${tag}' );"; + } + + try { + self::$pdo->exec( $query ); + } catch ( PDOException $exception ) { + EE::error( 'Encountered Error while inserting in "options" table: ' . $exception->getMessage(), false ); + } + } + + /** + * Execute drop table query for site and sitemeta table. + * + * @throws EE\ExitException + */ + public function down() { + + $query = "DELETE FROM options WHERE key LIKE 'easyengine/%';"; + + try { + self::$pdo->exec( $query ); + } catch ( PDOException $exception ) { + EE::error( 'Encountered Error while deleting from "options" table: ' . $exception->getMessage(), false ); + } + } +} diff --git a/migrations/db/20230312102550_easyengine_check_and_update_docker.php b/migrations/db/20230312102550_easyengine_check_and_update_docker.php new file mode 100644 index 000000000..0aff4ae36 --- /dev/null +++ b/migrations/db/20230312102550_easyengine_check_and_update_docker.php @@ -0,0 +1,51 @@ +stdout; + + if ( version_compare( $docker_version, '20.10.10', '<' ) ) { + EE::warning( 'Docker version should be 20.10.10 or above.' ); + + // If it is MacOS, prompt user to update docker. + if ( 'Darwin' === PHP_OS ) { + EE::confirm( 'Do you want to update Docker?' ); + EE::launch( 'open "docker://"' ); + } + + // If it is Linux, proceed with update. + if ( 'Linux' === PHP_OS ) { + EE::debug( 'Updating Docker...' ); + EE::launch( 'curl -fsSL https://get.docker.com | sh' ); + } + } + + // Check the version again post update. + $docker_version = EE::launch( 'docker version --format "{{.Server.Version}}"' )->stdout; + if ( version_compare( $docker_version, '20.10.10', '<' ) ) { + EE::error( 'Docker version should be 20.10.10 or above. Please update Docker and try again.' ); + } + } + + /** + * Execute drop table query for site and sitemeta table. + * + * @throws EE\ExitException + */ + public function down() { + + } +} diff --git a/migrations/db/20240502102550_easyengine_check_and_update_docker_one.php b/migrations/db/20240502102550_easyengine_check_and_update_docker_one.php new file mode 100644 index 000000000..9a25e20e4 --- /dev/null +++ b/migrations/db/20240502102550_easyengine_check_and_update_docker_one.php @@ -0,0 +1,87 @@ +is_first_execution ) { + $this->skip_this_migration = true; + } + } + + /** + * Execute create table query for site and sitemeta table. + * + * @throws EE\ExitException + */ + public function up() { + + if ( $this->skip_this_migration ) { + EE::debug( 'Skipping migration as it is not needed.' ); + + return; + } + + EE::debug( 'Checking Docker version.' ); + $docker_version = trim( EE::launch( 'docker version --format "{{.Server.Version}}"' )->stdout ); + + if ( version_compare( $docker_version, '20.10.10', '<' ) ) { + EE::warning( 'Docker version should be 20.10.10 or above.' ); + + // If it is MacOS, prompt user to update docker. + if ( 'Darwin' === PHP_OS ) { + EE::confirm( 'Do you want to update Docker?' ); + EE::launch( 'open "docker://"' ); + } + + // If it is Linux, proceed with update. + if ( 'Linux' === PHP_OS ) { + EE::debug( 'Updating Docker...' ); + EE::launch( 'curl -fsSL https://get.docker.com | sh' ); + } + } + + EE::debug( 'Checking docker-compose version' ); + $docker_compose_version = trim( EE::launch( 'docker-compose version --short' )->stdout ); + $docker_compose_path = EE::launch( 'command -v docker-compose' )->stdout; + $docker_compose_path = trim( $docker_compose_path ); + $docker_compose_backup_path = EE_BACKUP_DIR . '/docker-compose.backup'; + $docker_compose_new_path = EE_BACKUP_DIR . '/docker-compose'; + $fs = new Filesystem(); + if ( ! $fs->exists( EE_BACKUP_DIR ) ) { + $fs->mkdir( EE_BACKUP_DIR ); + } + $fs->copy( $docker_compose_path, $docker_compose_backup_path ); + + if ( version_compare( '1.29.2', $docker_compose_version, '!=' ) ) { + EE::exec( "curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m) -o $docker_compose_new_path && chmod +x $docker_compose_new_path" ); + EE::exec( "mv $docker_compose_new_path $docker_compose_path" ); + } + + // Check the version again post update. + $docker_version = trim( EE::launch( 'docker version --format "{{.Server.Version}}"' )->stdout ); + if ( version_compare( $docker_version, '20.10.10', '<' ) ) { + EE::error( 'Docker version should be 20.10.10 or above. Please update Docker and try again.' ); + } + + $docker_compose_version = trim( EE::launch( 'docker-compose version --short' )->stdout ); + if ( version_compare( '1.29.2', $docker_compose_version, '!=' ) ) { + EE::error( 'Docker-compose version should be 1.29.2. Please update Docker-compose and try again.' ); + } + } + + /** + * Execute drop table query for site and sitemeta table. + * + * @throws EE\ExitException + */ + public function down() { + + } +} diff --git a/php/EE/Autoloader.php b/php/EE/Autoloader.php new file mode 100644 index 000000000..f19e9228b --- /dev/null +++ b/php/EE/Autoloader.php @@ -0,0 +1,177 @@ +unregister(); + } + + /** + * Registers the autoload callback with the SPL autoload system. + */ + public function register() { + spl_autoload_register( array( $this, 'autoload' ) ); + } + + /** + * Unregisters the autoload callback with the SPL autoload system. + */ + public function unregister() { + spl_autoload_unregister( array( $this, 'autoload' ) ); + } + + /** + * Add a specific namespace structure with our custom autoloader. + * + * @param string $root Root namespace name. + * @param string $base_dir Directory containing the class files. + * @param string $prefix Prefix to be added before the class. + * @param string $suffix Suffix to be added after the class. + * @param boolean $lowercase Whether the class should be changed to + * lowercase. + * @param boolean $underscores Whether the underscores should be changed to + * hyphens. + * + * @return self + */ + public function add_namespace( + $root, + $base_dir, + $prefix = '', + $suffix = '.php', + $lowercase = false, + $underscores = false + ) { + $this->namespaces[] = array( + 'root' => $this->normalize_root( (string) $root ), + 'base_dir' => $this->add_trailing_slash( (string) $base_dir ), + 'prefix' => (string) $prefix, + 'suffix' => (string) $suffix, + 'lowercase' => (bool) $lowercase, + 'underscores' => (bool) $underscores, + ); + + return $this; + } + + /** + * The autoload function that gets registered with the SPL Autoloader + * system. + * + * @param string $class The class that got requested by the spl_autoloader. + */ + public function autoload( $class ) { + + // Iterate over namespaces to find a match. + foreach ( $this->namespaces as $namespace ) { + + // Move on if the object does not belong to the current namespace. + if ( 0 !== strpos( $class, $namespace['root'] ) ) { + continue; + } + + // Remove namespace root level to correspond with root filesystem, and + // replace the namespace separator "\" by the system-dependent directory separator. + $filename = str_replace( + array( $namespace['root'], '\\' ), array( '', DIRECTORY_SEPARATOR ), + $class + ); + + // Remove a leading backslash from the class name. + $filename = $this->remove_leading_backslash( $filename ); + + // Change to lower case if requested. + if ( $namespace['lowercase'] ) { + $filename = strtolower( $filename ); + } + + // Change underscores into hyphens if requested. + if ( $namespace['underscores'] ) { + $filename = str_replace( '_', '-', $filename ); + } + + // Add base_dir, prefix and suffix. + $filepath = $namespace['base_dir'] + . $namespace['prefix'] + . $filename + . $namespace['suffix']; + + // Throw an exception if the file does not exist or is not readable. + if ( is_readable( $filepath ) ) { + require_once $filepath; + } + } + } + + /** + * Normalize a namespace root. + * + * @param string $root Namespace root that needs to be normalized. + * + * @return string Normalized namespace root. + */ + protected function normalize_root( $root ) { + $root = $this->remove_leading_backslash( $root ); + + return $this->add_trailing_backslash( $root ); + } + + /** + * Remove a leading backslash from a string. + * + * @param string $string String to remove the leading backslash from. + * + * @return string Modified string. + */ + protected function remove_leading_backslash( $string ) { + return ltrim( $string, '\\' ); + } + + /** + * Make sure a string ends with a trailing backslash. + * + * @param string $string String to check the trailing backslash of. + * + * @return string Modified string. + */ + protected function add_trailing_backslash( $string ) { + return rtrim( $string, '\\' ) . '\\'; + } + + /** + * Make sure a string ends with a trailing slash. + * + * @param string $string String to check the trailing slash of. + * + * @return string Modified string. + */ + protected function add_trailing_slash( $string ) { + return rtrim( $string, '/\\' ) . '/'; + } +} diff --git a/php/EE/Bootstrap/AutoloaderStep.php b/php/EE/Bootstrap/AutoloaderStep.php new file mode 100644 index 000000000..275c6d03f --- /dev/null +++ b/php/EE/Bootstrap/AutoloaderStep.php @@ -0,0 +1,96 @@ +state = $state; + + $found_autoloader = false; + $autoloader_paths = $this->get_autoloader_paths(); + + if ( false === $autoloader_paths ) { + // Skip this autoloading step. + return $state; + } + + foreach ( $autoloader_paths as $autoloader_path ) { + if ( is_readable( $autoloader_path ) ) { + try { + require $autoloader_path; + $found_autoloader = true; + } catch ( \Exception $exception ) { + \EE::warning( + "Failed to load autoloader '{$autoloader_path}'. Reason: " + . $exception->getMessage() + ); + } + } + } + + if ( ! $found_autoloader ) { + $this->handle_failure(); + } + + return $this->state; + } + + /** + * Get the name of the custom vendor folder as set in `composer.json`. + * + * @return string|false Name of the custom vendor folder or false if none. + */ + protected function get_custom_vendor_folder() { + $maybe_composer_json = EE_ROOT . '/../../../composer.json'; + if ( ! is_readable( $maybe_composer_json ) ) { + return false; + } + + $composer = json_decode( file_get_contents( $maybe_composer_json ) ); + + if ( ! empty( $composer->config ) + && ! empty( $composer->config->{'vendor-dir'} ) + ) { + return $composer->config->{'vendor-dir'}; + } + + return false; + } + + /** + * Handle the failure to find an autoloader. + * + * @return void + */ + protected function handle_failure() { } + + /** + * Get the autoloader paths to scan for an autoloader. + * + * @return string[]|false Array of strings with autoloader paths, or false + * to skip. + */ + abstract protected function get_autoloader_paths(); +} diff --git a/php/EE/Bootstrap/BootstrapState.php b/php/EE/Bootstrap/BootstrapState.php new file mode 100644 index 000000000..e103e88e4 --- /dev/null +++ b/php/EE/Bootstrap/BootstrapState.php @@ -0,0 +1,56 @@ +state ) + ? $this->state[ $key ] + : $fallback; + } + + /** + * Set the state value for a given key. + * + * @param string $key Key to set the state for. + * @param mixed $value Value to set the state for the given key to. + * + * @return void + */ + // @codingStandardsIgnoreLine + public function setValue( $key, $value ) { + $this->state[ $key ] = $value; + } +} diff --git a/php/EE/Bootstrap/BootstrapStep.php b/php/EE/Bootstrap/BootstrapStep.php new file mode 100644 index 000000000..6598d49d8 --- /dev/null +++ b/php/EE/Bootstrap/BootstrapStep.php @@ -0,0 +1,22 @@ +init_config(); + + return $state; + } +} diff --git a/php/EE/Bootstrap/DeclareAbstractBaseCommand.php b/php/EE/Bootstrap/DeclareAbstractBaseCommand.php new file mode 100644 index 000000000..5d34b78f3 --- /dev/null +++ b/php/EE/Bootstrap/DeclareAbstractBaseCommand.php @@ -0,0 +1,26 @@ +get_protected_commands(); + $current_command = $this->get_current_command(); + + foreach ( $commands as $command ) { + if ( 0 === strpos( $current_command, $command ) ) { + $state->setValue( BootstrapState::IS_PROTECTED_COMMAND, true ); + } + } + + return $state; + } + + /** + * Get the list of protected commands. + * + * @return array + */ + private function get_protected_commands() { + return array( + 'cli info', + 'package', + ); + } + + /** + * Get the current command as a string. + * + * @return string Current command to be executed. + */ + private function get_current_command() { + $runner = new RunnerInstance(); + + return implode( ' ', (array) $runner()->arguments ); + } +} diff --git a/php/EE/Bootstrap/IncludeFallbackAutoloader.php b/php/EE/Bootstrap/IncludeFallbackAutoloader.php new file mode 100644 index 000000000..bb550bed5 --- /dev/null +++ b/php/EE/Bootstrap/IncludeFallbackAutoloader.php @@ -0,0 +1,45 @@ +get_custom_vendor_folder() ) { + array_unshift( + $autoloader_paths, + EE_ROOT . '/../../../' . $custom_vendor . '/autoload.php' + ); + } + + EE::debug( + sprintf( + 'Fallback autoloader paths: %s', + implode( ', ', $autoloader_paths ) + ), + 'bootstrap' + ); + + return $autoloader_paths; + } +} diff --git a/php/EE/Bootstrap/IncludeFrameworkAutoloader.php b/php/EE/Bootstrap/IncludeFrameworkAutoloader.php new file mode 100644 index 000000000..13303b360 --- /dev/null +++ b/php/EE/Bootstrap/IncludeFrameworkAutoloader.php @@ -0,0 +1,56 @@ + EE_ROOT . '/php/EE', + 'cli' => EE_VENDOR_DIR . '/wp-cli/php-cli-tools/lib/cli', + 'WpOrg\\Requests' => EE_VENDOR_DIR . '/rmccue/requests/src', // New PSR-4 mapping + 'Symfony\Component\Finder' => EE_VENDOR_DIR . '/symfony/finder/', + 'Psr\Log' => EE_VENDOR_DIR . '/psr/log/Psr/Log/', + 'Monolog' => EE_VENDOR_DIR . '/monolog/monolog/src/Monolog', + ]; + + foreach ( $mappings as $namespace => $folder ) { + $autoloader->add_namespace( + $namespace, + $folder + ); + } + + include_once EE_VENDOR_DIR . '/wp-cli/mustangostang-spyc/Spyc.php'; + + $autoloader->register(); + + return $state; + } +} diff --git a/php/EE/Bootstrap/IncludePackageAutoloader.php b/php/EE/Bootstrap/IncludePackageAutoloader.php new file mode 100644 index 000000000..2fdbca737 --- /dev/null +++ b/php/EE/Bootstrap/IncludePackageAutoloader.php @@ -0,0 +1,57 @@ +state->getValue( BootstrapState::IS_PROTECTED_COMMAND, $fallback = false ) ) { + return false; + } + + $runner = new RunnerInstance(); + //$skip_packages = $runner()->config['skip-packages']; + // if ( true === $skip_packages ) { + // \EE::debug( 'Skipped loading packages.', 'bootstrap' ); + + // return false; + // } + + $autoloader_path = $runner()->get_packages_dir_path() . 'vendor/autoload.php'; + + if ( is_readable( $autoloader_path ) ) { + \EE::debug( + 'Loading packages from: ' . $autoloader_path, + 'bootstrap' + ); + + return array( + $autoloader_path, + ); + } + + return false; + } + + /** + * Handle the failure to find an autoloader. + * + * @return void + */ + protected function handle_failure() { + \EE::debug( 'No package autoload found to load.', 'bootstrap' ); + } +} diff --git a/php/EE/Bootstrap/InitializeColorization.php b/php/EE/Bootstrap/InitializeColorization.php new file mode 100644 index 000000000..4b723730b --- /dev/null +++ b/php/EE/Bootstrap/InitializeColorization.php @@ -0,0 +1,27 @@ +init_colorization(); + + return $state; + } +} diff --git a/php/EE/Bootstrap/InitializeLogger.php b/php/EE/Bootstrap/InitializeLogger.php new file mode 100644 index 000000000..2a209a2cd --- /dev/null +++ b/php/EE/Bootstrap/InitializeLogger.php @@ -0,0 +1,47 @@ +declare_loggers(); + $runner = new RunnerInstance(); + $runner()->init_logger(); + + return $state; + } + + /** + * Load the class declarations for the loggers. + */ + private function declare_loggers() { + $logger_dir = EE_ROOT . '/php/EE/Loggers'; + $iterator = new \DirectoryIterator( $logger_dir ); + + // Make sure the base class is declared first. + include_once "$logger_dir/Base.php"; + + foreach ( $iterator as $filename ) { + if ( '.php' !== substr( $filename, - 4 ) ) { + continue; + } + + include_once "$logger_dir/$filename"; + } + } +} diff --git a/php/EE/Bootstrap/LaunchRunner.php b/php/EE/Bootstrap/LaunchRunner.php new file mode 100644 index 000000000..01e956edc --- /dev/null +++ b/php/EE/Bootstrap/LaunchRunner.php @@ -0,0 +1,27 @@ +start(); + + return $state; + } +} diff --git a/php/EE/Bootstrap/LoadDispatcher.php b/php/EE/Bootstrap/LoadDispatcher.php new file mode 100644 index 000000000..728c97e62 --- /dev/null +++ b/php/EE/Bootstrap/LoadDispatcher.php @@ -0,0 +1,26 @@ +` option. + * + * @package EE\Bootstrap + */ +final class LoadRequiredCommand implements BootstrapStep { + + /** + * Process this single bootstrapping step. + * + * @param BootstrapState $state Contextual state to pass into the step. + * + * @return BootstrapState Modified state to pass to the next step. + */ + public function process( BootstrapState $state ) { + if ( $state->getValue( BootstrapState::IS_PROTECTED_COMMAND, $fallback = false ) ) { + return $state; + } + + $runner = new RunnerInstance(); + if ( ! isset( $runner()->config['require'] ) ) { + return $state; + } + + foreach ( $runner()->config['require'] as $path ) { + if ( ! file_exists( $path ) ) { + $context = ''; + $required_files = $runner()->get_required_files(); + foreach ( array( 'global', 'project', 'runtime' ) as $scope ) { + if ( in_array( $path, $required_files[ $scope ], true ) ) { + switch ( $scope ) { + case 'global': + $context = ' (from global ' . Utils\basename( $runner()->get_global_config_path() ) . ')'; + break; + case 'project': + $context = ' (from project\'s ' . Utils\basename( $runner()->get_project_config_path() ) . ')'; + break; + case 'runtime': + $context = ' (from runtime argument)'; + break; + } + break; + } + } + EE::error( sprintf( "Required file '%s' doesn't exist%s.", Utils\basename( $path ), $context ) ); + } + Utils\load_file( $path ); + EE::debug( 'Required file from config: ' . $path, 'bootstrap' ); + } + + return $state; + } +} diff --git a/php/EE/Bootstrap/LoadUtilityFunctions.php b/php/EE/Bootstrap/LoadUtilityFunctions.php new file mode 100644 index 000000000..3eeb1752f --- /dev/null +++ b/php/EE/Bootstrap/LoadUtilityFunctions.php @@ -0,0 +1,26 @@ +add_deferred_commands(); + + // Process deferred command additions for commands added through + // plugins. + \EE::add_hook( + 'find_command_to_run_pre', + array( $this, 'add_deferred_commands' ) + ); + + return $state; + } + + /** + * Add deferred commands that are still waiting to be processed. + */ + public function add_deferred_commands() { + $deferred_additions = \EE::get_deferred_additions(); + + foreach ( $deferred_additions as $name => $addition ) { + \EE::add_command( + $name, + $addition['callable'], + $addition['args'] + ); + } + } +} diff --git a/php/EE/Bootstrap/RegisterFrameworkCommands.php b/php/EE/Bootstrap/RegisterFrameworkCommands.php new file mode 100644 index 000000000..7814accec --- /dev/null +++ b/php/EE/Bootstrap/RegisterFrameworkCommands.php @@ -0,0 +1,42 @@ +getMessage() + ); + } + } + + return $state; + } +} diff --git a/php/EE/Bootstrap/RunnerInstance.php b/php/EE/Bootstrap/RunnerInstance.php new file mode 100644 index 000000000..12649f8a9 --- /dev/null +++ b/php/EE/Bootstrap/RunnerInstance.php @@ -0,0 +1,32 @@ +words = explode( ' ', $line ); + + // first word is always `ee` + array_shift( $this->words ); + + // last word is either empty or an incomplete subcommand + $this->cur_word = end( $this->words ); + if ( '' !== $this->cur_word && ! preg_match( '/^\-/', $this->cur_word ) ) { + array_pop( $this->words ); + } + + $is_alias = false; + $is_help = false; + if ( ! empty( $this->words[0] ) && preg_match( '/^@/', $this->words[0] ) ) { + array_shift( $this->words ); + // `ee @al` is false, but `ee @all ` is true. + if ( count( $this->words ) ) { + $is_alias = true; + } + } elseif ( ! empty( $this->words[0] ) && 'help' === $this->words[0] ) { + array_shift( $this->words ); + $is_help = true; + } + + $r = $this->get_command( $this->words ); + if ( ! is_array( $r ) ) { + return; + } + + list( $command, $args, $assoc_args ) = $r; + + $spec = SynopsisParser::parse( $command->get_synopsis() ); + + foreach ( $spec as $arg ) { + if ( 'positional' === $arg['type'] && 'file' === $arg['name'] ) { + $this->add( ' ' ); + return; + } + } + if ( $command->can_have_subcommands() ) { + // add completion when command is `ee` and alias isn't set. + if ( 'ee' === $command->get_name() && false === $is_alias && false === $is_help ) { + $aliases = \EE::get_configurator()->get_aliases(); + foreach ( $aliases as $name => $_ ) { + $this->add( "$name " ); + } + } + foreach ( $command->get_subcommands() as $name => $subcommand ) { + if ( $shell === 'zsh') { + $this->add( $name . ':' . $subcommand->get_shortdesc() ); + } else { + $this->add( $name ); + } + } + } else { + foreach ( $spec as $arg ) { + if ( in_array( $arg['type'], array( 'flag', 'assoc' ), true ) ) { + if ( isset( $assoc_args[ $arg['name'] ] ) ) { + continue; + } + + $opt = "--{$arg['name']}"; + + if ( 'flag' === $arg['type'] ) { + $opt .= ' '; + } elseif ( ! $arg['value']['optional'] ) { + $opt .= '='; + } + + $this->add( $opt ); + } + } + } + } + + private function get_command( $words ) { + $positional_args = $assoc_args = array(); + + foreach ( $words as $arg ) { + if ( preg_match( '|^--([^=]+)=?|', $arg, $matches ) ) { + $assoc_args[ $matches[1] ] = true; + } else { + $positional_args[] = $arg; + } + } + + $this->maybe_add_site_command( $words ); + + $r = \EE::get_runner()->find_command_to_run( $positional_args ); + if ( ! is_array( $r ) && array_pop( $positional_args ) === $this->cur_word ) { + $r = \EE::get_runner()->find_command_to_run( $positional_args ); + } + + if ( ! is_array( $r ) ) { + return $r; + } + + list( $command, $args ) = $r; + + return array( $command, $args, $assoc_args ); + } + + /** + * Adds correct site-type to EE runner if autocompletion of site command is required + */ + private function maybe_add_site_command( array $words ) { + if ( count( $words ) > 0 && 'site' === $words[0] ) { + $type = $this->get_site_type( $words, 'html' ); + $site_types = \Site_Command::get_site_types(); + + $command = EE::get_root_command(); + $callback = $site_types[ $type ]; + $leaf_command = CommandFactory::create( 'site', $callback, $command ); + $command->add_subcommand( 'site', $leaf_command ); + } + } + + /** + * Returns correct site-type for completion. Only for `site create`, type is specified in command. + * For other commands, it is fetched from EE db.comp + */ + private function get_site_type( $words, $default_site_type ) { + $type = $default_site_type; + + foreach ( $words as $arg ) { + if ( preg_match( '|^--type=(\S+)|', $arg, $matches ) ) { + $type = $matches[1]; + } + } + + if ( count( $words ) >= 3 && 'create' === $words[1] && ! preg_match( '|^--|', $words[2] ) ) { + $sitename = str_replace( array( 'https://', 'http://' ), '', $words[2] ); + $sitetype = Site::find( $sitename, array( 'site_type' ) ); + + if ( $sitetype ) { + $type = $sitetype->site_type; + } + } + + return $type; + } + + private function add( $opt ) { + if ( '' !== $this->cur_word ) { + if ( 0 !== strpos( $opt, $this->cur_word ) ) { + return; + } + } + + $this->opts[] = $opt; + } + + public function render() { + foreach ( $this->opts as $opt ) { + \EE::line( $opt ); + } + } +} diff --git a/php/EE/ComposerIO.php b/php/EE/ComposerIO.php new file mode 100644 index 000000000..83ab7738f --- /dev/null +++ b/php/EE/ComposerIO.php @@ -0,0 +1,39 @@ +]+)>#', '$1$2', $message ); + EE::log( strip_tags( trim( $message ) ) ); + } + +} diff --git a/php/EE/Configurator.php b/php/EE/Configurator.php new file mode 100644 index 000000000..f4b142484 --- /dev/null +++ b/php/EE/Configurator.php @@ -0,0 +1,344 @@ +spec = include $path; + + $defaults = array( + 'runtime' => false, + 'file' => false, + 'synopsis' => '', + 'default' => null, + 'multiple' => false, + ); + + foreach ( $this->spec as $key => &$details ) { + $details = array_merge( $defaults, $details ); + + $this->config[ $key ] = $details['default']; + } + } + + /** + * Get declared configuration values as an array. + * + * @return array + */ + public function to_array() { + return array( $this->config, $this->extra_config ); + } + + /** + * Get configuration specification, i.e. list of accepted keys. + * + * @return array + */ + public function get_spec() { + return $this->spec; + } + + /** + * Get any aliases defined in config files. + * + * @return array + */ + public function get_aliases() { + if ( $runtime_alias = getenv( 'EE_RUNTIME_ALIAS' ) ) { + $returned_aliases = array(); + foreach ( json_decode( $runtime_alias, true ) as $key => $value ) { + if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { + $returned_aliases[ $key ] = array(); + foreach ( self::$alias_spec as $i ) { + if ( isset( $value[ $i ] ) ) { + $returned_aliases[ $key ][ $i ] = $value[ $i ]; + } + } + } + } + return $returned_aliases; + } + + return $this->aliases; + } + + /** + * Splits a list of arguments into positional, associative and config. + * + * @param array(string) + * @return array(array) + */ + public function parse_args( $arguments ) { + list( $positional_args, $mixed_args, $global_assoc, $local_assoc ) = self::extract_assoc( $arguments ); + list( $assoc_args, $runtime_config ) = $this->unmix_assoc_args( $mixed_args, $global_assoc, $local_assoc ); + return array( $positional_args, $assoc_args, $runtime_config ); + } + + /** + * Splits positional args from associative args. + * + * @param array + * @return array(array) + */ + public static function extract_assoc( $arguments ) { + $positional_args = $assoc_args = $global_assoc = $local_assoc = array(); + + foreach ( $arguments as $arg ) { + $positional_arg = $assoc_arg = null; + + if ( preg_match( '|^--no-([^=]+)$|', $arg, $matches ) ) { + $assoc_arg = array( $matches[1], false ); + } elseif ( preg_match( '|^--([^=]+)$|', $arg, $matches ) ) { + $assoc_arg = array( $matches[1], true ); + } elseif ( preg_match( '|^--([^=]+)=(.*)|s', $arg, $matches ) ) { + $assoc_arg = array( $matches[1], $matches[2] ); + } else { + $positional = $arg; + } + + if ( ! is_null( $assoc_arg ) ) { + $assoc_args[] = $assoc_arg; + if ( count( $positional_args ) ) { + $local_assoc[] = $assoc_arg; + } else { + $global_assoc[] = $assoc_arg; + } + } elseif ( ! is_null( $positional ) ) { + $positional_args[] = $positional; + } + } + + return array( $positional_args, $assoc_args, $global_assoc, $local_assoc ); + } + + /** + * Separate runtime parameters from command-specific parameters. + * + * @param array $mixed_args + * @return array + */ + private function unmix_assoc_args( $mixed_args, $global_assoc = array(), $local_assoc = array() ) { + $assoc_args = $runtime_config = array(); + + if ( getenv( 'EE_STRICT_ARGS_MODE' ) ) { + foreach ( $global_assoc as $tmp ) { + list( $key, $value ) = $tmp; + if ( isset( $this->spec[ $key ] ) && false !== $this->spec[ $key ]['runtime'] ) { + $this->assoc_arg_to_runtime_config( $key, $value, $runtime_config ); + } + } + foreach ( $local_assoc as $tmp ) { + $assoc_args[ $tmp[0] ] = $tmp[1]; + } + } else { + foreach ( $mixed_args as $tmp ) { + list( $key, $value ) = $tmp; + + if ( isset( $this->spec[ $key ] ) && false !== $this->spec[ $key ]['runtime'] ) { + $this->assoc_arg_to_runtime_config( $key, $value, $runtime_config ); + } else { + $assoc_args[ $key ] = $value; + } + } + } + + return array( $assoc_args, $runtime_config ); + } + + /** + * Handle turning an $assoc_arg into a runtime arg. + */ + private function assoc_arg_to_runtime_config( $key, $value, &$runtime_config ) { + $details = $this->spec[ $key ]; + if ( isset( $details['deprecated'] ) ) { + fwrite( STDERR, "EE: The --{$key} global parameter is deprecated. {$details['deprecated']}\n" ); + } + + if ( $details['multiple'] ) { + $runtime_config[ $key ][] = $value; + } else { + $runtime_config[ $key ] = $value; + } + } + + /** + * Load a YAML file of parameters into scope. + * + * @param string $path Path to YAML file. + */ + public function merge_yml( $path, $current_alias = null ) { + $yaml = self::load_yml( $path ); + if ( ! empty( $yaml['_']['inherit'] ) ) { + $this->merge_yml( $yaml['_']['inherit'], $current_alias ); + } + // Prepare the base path for absolutized alias paths + $yml_file_dir = $path ? dirname( $path ) : false; + foreach ( $yaml as $key => $value ) { + if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { + $this->aliases[ $key ] = array(); + $is_alias = false; + foreach ( self::$alias_spec as $i ) { + if ( isset( $value[ $i ] ) ) { + if ( 'path' === $i && ! isset( $value['ssh'] ) ) { + self::absolutize( $value[ $i ], $yml_file_dir ); + } + $this->aliases[ $key ][ $i ] = $value[ $i ]; + $is_alias = true; + } + } + // If it's not an alias, it might be a group of aliases + if ( ! $is_alias && is_array( $value ) ) { + $alias_group = array(); + foreach ( $value as $i => $k ) { + if ( preg_match( '#' . self::ALIAS_REGEX . '#', $k ) ) { + $alias_group[] = $k; + } + } + $this->aliases[ $key ] = $alias_group; + } + } elseif ( ! isset( $this->spec[ $key ] ) || false === $this->spec[ $key ]['file'] ) { + if ( isset( $this->extra_config[ $key ] ) + && ! empty( $yaml['_']['merge'] ) + && is_array( $this->extra_config[ $key ] ) + && is_array( $value ) ) { + $this->extra_config[ $key ] = array_merge( $this->extra_config[ $key ], $value ); + } else { + $this->extra_config[ $key ] = $value; + } + } elseif ( $this->spec[ $key ]['multiple'] ) { + self::arrayify( $value ); + $this->config[ $key ] = array_merge( $this->config[ $key ], $value ); + } else { + if ( $current_alias && in_array( $key, self::$alias_spec, true ) ) { + continue; + } + $this->config[ $key ] = $value; + } + } + } + + /** + * Merge an array of values into the configurator config. + * + * @param array $config + */ + public function merge_array( $config ) { + foreach ( $this->spec as $key => $details ) { + if ( false !== $details['runtime'] && isset( $config[ $key ] ) ) { + $value = $config[ $key ]; + + if ( 'require' == $key ) { + $value = \EE\Utils\expand_globs( $value ); + } + + if ( $details['multiple'] ) { + self::arrayify( $value ); + $this->config[ $key ] = array_merge( $this->config[ $key ], $value ); + } else { + $this->config[ $key ] = $value; + } + } + } + } + + /** + * Load values from a YAML file. + * + * @param string $yml_file Path to the YAML file + * @return array $config Declared configuration values + */ + private static function load_yml( $yml_file ) { + if ( ! $yml_file ) { + return array(); + } + + $config = Spyc::YAMLLoad( $yml_file ); + + // Make sure config-file-relative paths are made absolute. + $yml_file_dir = dirname( $yml_file ); + + if ( isset( $config['path'] ) ) { + self::absolutize( $config['path'], $yml_file_dir ); + } + + if ( isset( $config['require'] ) ) { + self::arrayify( $config['require'] ); + $config['require'] = \EE\Utils\expand_globs( $config['require'] ); + foreach ( $config['require'] as &$path ) { + self::absolutize( $path, $yml_file_dir ); + } + } + + return $config; + } + + /** + * Conform a variable to an array. + * + * @param mixed $val A string or an array + */ + private static function arrayify( &$val ) { + $val = (array) $val; + } + + /** + * Make a path absolute. + * + * @param string $path Path to file. + * @param string $base Base path to prepend. + */ + private static function absolutize( &$path, $base ) { + if ( ! empty( $path ) && ! \EE\Utils\is_path_absolute( $path ) ) { + $path = $base . DIRECTORY_SEPARATOR . $path; + } + } + +} diff --git a/php/EE/Dispatcher/CommandAddition.php b/php/EE/Dispatcher/CommandAddition.php new file mode 100644 index 000000000..58277230a --- /dev/null +++ b/php/EE/Dispatcher/CommandAddition.php @@ -0,0 +1,58 @@ +abort = true; + $this->reason = (string) $reason; + } + + /** + * Check whether the command addition was aborted. + * + * @return bool + */ + public function was_aborted() { + return $this->abort; + } + + /** + * Get the reason as to why the addition was aborted. + * + * @return string + */ + public function get_reason() { + return $this->reason; + } +} diff --git a/php/EE/Dispatcher/CommandFactory.php b/php/EE/Dispatcher/CommandFactory.php new file mode 100644 index 000000000..b476dc7ca --- /dev/null +++ b/php/EE/Dispatcher/CommandFactory.php @@ -0,0 +1,258 @@ +getMethod( $callable[1] ) + ); + } else { + $reflection = new \ReflectionClass( $callable ); + if ( $reflection->isSubclassOf( '\EE\Dispatcher\CommandNamespace' ) ) { + $command = self::create_namespace( $parent, $name, $callable ); + } elseif ( $reflection->hasMethod( '__invoke' ) ) { + $class = is_object( $callable ) ? $callable : $reflection->name; + $command = self::create_subcommand( + $parent, $name, array( $class, '__invoke' ), + $reflection->getMethod( '__invoke' ) + ); + } else { + $command = self::create_composite_command( $parent, $name, $callable ); + } + } + + return $command; + } + + /** + * Clear the file contents cache. + */ + public static function clear_file_contents_cache() { + self::$file_contents = array(); + } + + /** + * Create a new Subcommand instance. + * + * @param mixed $parent The new command's parent Composite command + * @param string $name Represents how the command should be invoked + * @param mixed $callable A callable function or closure, or class name and method + * @param object $reflection Reflection instance, for doc parsing + * @param string $class A subclass of EE_Command + * @param string $method Class method to be called upon invocation. + */ + private static function create_subcommand( $parent, $name, $callable, $reflection ) { + $doc_comment = self::get_doc_comment( $reflection ); + $docparser = self::get_inherited_docparser( $doc_comment, $reflection ); + + while ( $docparser->has_tag( 'inheritdoc' ) ) { + $inherited_method = $reflection->getDeclaringClass()->getParentClass()->getMethod( $reflection->name ); + + $doc_comment = self::get_doc_comment( $inherited_method ); + $docparser = new \EE\DocParser( $doc_comment ); + } + + if ( is_array( $callable ) ) { + if ( ! $name ) { + $name = $docparser->get_tag( 'subcommand' ); + } + + if ( ! $name ) { + $name = $reflection->name; + } + } + if ( ! $doc_comment ) { + \EE::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); + } + + $when_invoked = function ( $args, $assoc_args ) use ( $callable ) { + if ( is_array( $callable ) ) { + $callable[0] = is_object( $callable[0] ) ? $callable[0] : new $callable[0]; + call_user_func( array( $callable[0], $callable[1] ), $args, $assoc_args ); + } else { + call_user_func( $callable, $args, $assoc_args ); + } + }; + + return new Subcommand( $parent, $name, $docparser, $when_invoked ); + } + + /** + * Create a new Composite command instance. + * + * @param mixed $parent The new command's parent Root or Composite command + * @param string $name Represents how the command should be invoked + * @param mixed $callable + */ + private static function create_composite_command( $parent, $name, $callable ) { + $reflection = new \ReflectionClass( $callable ); + $doc_comment = self::get_doc_comment( $reflection ); + if ( ! $doc_comment ) { + \EE::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); + } + $docparser = new \EE\DocParser( $doc_comment ); + + $container = new CompositeCommand( $parent, $name, $docparser ); + + foreach ( $reflection->getMethods() as $method ) { + $method_doc_comment = self::get_doc_comment( $method ); + if ( ! self::is_good_method( $method ) || self::should_ignore_method( $method_doc_comment, $method ) ) { + continue; + } + + $class = is_object( $callable ) ? $callable : $reflection->name; + $subcommand = self::create_subcommand( $container, false, array( $class, $method->name ), $method ); + + $subcommand_name = $subcommand->get_name(); + + $container->add_subcommand( $subcommand_name, $subcommand ); + } + + return $container; + } + + /** + * Create a new command namespace instance. + * + * @param mixed $parent The new namespace's parent Root or Composite command. + * @param string $name Represents how the command should be invoked + * @param mixed $callable + */ + private static function create_namespace( $parent, $name, $callable ) { + $reflection = new \ReflectionClass( $callable ); + $doc_comment = self::get_doc_comment( $reflection ); + if ( ! $doc_comment ) { + \EE::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); + } + $docparser = new \EE\DocParser( $doc_comment ); + + return new CommandNamespace( $parent, $name, $docparser ); + } + + /** + * Check whether a method is actually callable. + * + * @param ReflectionMethod $method + * @return bool + */ + private static function is_good_method( $method ) { + return $method->isPublic() && ! $method->isStatic() && 0 !== strpos( $method->getName(), '__' ); + } + + /** + * @param string $doc_comment + * @param ReflectionMethod $reflection + */ + private static function get_inherited_docparser( $doc_comment, $reflection ) { + $docparser = new \EE\DocParser( $doc_comment ); + while ( $docparser->has_tag( 'inheritdoc' ) ) { + $inherited_method = $reflection->getDeclaringClass()->getParentClass()->getMethod( $reflection->name ); + + $doc_comment = self::get_doc_comment( $inherited_method ); + $docparser = new \EE\DocParser( $doc_comment ); + } + + return $docparser; + } + + /** + * Check whether a method should be ignored. + * + * @param ReflectionMethod $method + * + * @return bool + */ + private static function should_ignore_method( $doc_comment, $reflection ) { + $docparser = self::get_inherited_docparser( $doc_comment, $reflection ); + + return $docparser->has_tag( 'ignorecommand' ); + } + + /** + * Gets the document comment. Caters for PHP directive `opcache.save comments` being disabled. + * + * @param ReflectionMethod|ReflectionClass|ReflectionFunction $reflection Reflection instance. + * @return string|false|null Doc comment string if any, false if none (same as `Reflection*::getDocComment()`), null if error. + */ + private static function get_doc_comment( $reflection ) { + $doc_comment = $reflection->getDocComment(); + + if ( false !== $doc_comment || ! ( ini_get( 'opcache.enable_cli' ) && ! ini_get( 'opcache.save_comments' ) ) ) { + // Either have doc comment, or no doc comment and save comments enabled - standard situation. + if ( ! getenv( 'EE_TEST_GET_DOC_COMMENT' ) ) { + return $doc_comment; + } + } + + $filename = $reflection->getFileName(); + + if ( isset( self::$file_contents[ $filename ] ) ) { + $contents = self::$file_contents[ $filename ]; + } elseif ( is_readable( $filename ) && ( $contents = file_get_contents( $filename ) ) ) { + self::$file_contents[ $filename ] = $contents = explode( "\n", $contents ); + } else { + \EE::debug( "Could not read contents for filename '{$filename}'.", 'commandfactory' ); + return null; + } + + return self::extract_last_doc_comment( implode( "\n", array_slice( $contents, 0, $reflection->getStartLine() ) ) ); + } + + /** + * Returns the last doc comment if any in `$content`. + * + * @param string $content The content, which should end at the class or function declaration. + * @return string|bool The last doc comment if any, or false if none. + */ + private static function extract_last_doc_comment( $content ) { + $content = trim( $content ); + $comment_end_pos = strrpos( $content, '*/' ); + if ( false === $comment_end_pos ) { + return false; + } + // Make sure comment end belongs to this class/function. + if ( preg_match_all( '/(?:^|[\s;}])(?:class|function)\s+/', substr( $content, $comment_end_pos + 2 ), $dummy /*needed for PHP 5.3*/ ) > 1 ) { + return false; + } + $content = substr( $content, 0, $comment_end_pos + 2 ); + if ( false === ( $comment_start_pos = strrpos( $content, '/**' ) ) || $comment_start_pos + 2 === $comment_end_pos ) { + return false; + } + // Make sure comment start belongs to this comment end. + if ( false !== ( $comment_end2_pos = strpos( substr( $content, $comment_start_pos ), '*/' ) ) && $comment_start_pos + $comment_end2_pos < $comment_end_pos ) { + return false; + } + // Allow for '/**' within doc comment. + $subcontent = substr( $content, 0, $comment_start_pos ); + while ( false !== ( $comment_start2_pos = strrpos( $subcontent, '/**' ) ) && false === strpos( $subcontent, '*/', $comment_start2_pos ) ) { + $comment_start_pos = $comment_start2_pos; + $subcontent = substr( $subcontent, 0, $comment_start_pos ); + } + return substr( $content, $comment_start_pos, $comment_end_pos + 2 ); + } +} diff --git a/php/EE/Dispatcher/CommandNamespace.php b/php/EE/Dispatcher/CommandNamespace.php new file mode 100644 index 000000000..fc18b7a91 --- /dev/null +++ b/php/EE/Dispatcher/CommandNamespace.php @@ -0,0 +1,50 @@ +get_subcommands(); + + $i = 0; + $count = 0; + + foreach ( $methods as $name => $subcommand ) { + $prefix = ( 0 == $i++ ) ? 'usage: ' : ' or: '; + + if ( \EE::get_runner()->is_command_disabled( $subcommand ) ) { + continue; + } + + \EE::line( $subcommand->get_usage( $prefix ) ); + $count++; + } + + $cmd_name = implode( ' ', array_slice( get_path( $this ), 1 ) ); + $message = $count > 0 + ? "See 'ee help $cmd_name ' for more information on a specific command." + : "The namespace $cmd_name does not contain any usable commands in the current context."; + + \EE::line(); + \EE::line( $message ); + + } +} diff --git a/php/EE/Dispatcher/CompositeCommand.php b/php/EE/Dispatcher/CompositeCommand.php new file mode 100644 index 000000000..261282cd9 --- /dev/null +++ b/php/EE/Dispatcher/CompositeCommand.php @@ -0,0 +1,307 @@ +parent = $parent; + + $this->name = $name; + + $this->shortdesc = $docparser->get_shortdesc(); + $this->longdesc = $docparser->get_longdesc(); + $this->docparser = $docparser; + + $when_to_invoke = $docparser->get_tag( 'when' ); + if ( $when_to_invoke ) { + \EE::get_runner()->register_early_invoke( $when_to_invoke, $this ); + } + } + + /** + * Get the parent composite (or root) command + * + * @return mixed + */ + public function get_parent() { + return $this->parent; + } + + /** + * Add a named subcommand to this composite command's + * set of contained subcommands. + * + * @param string $name Represents how subcommand should be invoked + * @param \EE\Dispatcher\Subcommand + */ + public function add_subcommand( $name, $command ) { + $this->subcommands[ $name ] = $command; + } + + /** + * Remove a named subcommand from this composite command's set of contained + * subcommands + * + * @param string $name Represents how subcommand should be invoked + */ + public function remove_subcommand( $name ) { + if ( isset( $this->subcommands[ $name ] ) ) { + unset( $this->subcommands[ $name ] ); + } + } + + + /** + * Composite commands always contain subcommands. + * + * @return true + */ + public function can_have_subcommands() { + return true; + } + + /** + * Get the subcommands contained by this composite + * command. + * + * @return array + */ + public function get_subcommands() { + ksort( $this->subcommands ); + + return $this->subcommands; + } + + /** + * Get the name of this composite command. + * + * @return string + */ + public function get_name() { + return $this->name; + } + + /** + * Get the short description for this composite + * command. + * + * @return string + */ + public function get_shortdesc() { + return $this->shortdesc; + } + + /** + * Set the short description for this composite command. + * + * @param string + */ + public function set_shortdesc( $shortdesc ) { + $this->shortdesc = Utils\normalize_eols( $shortdesc ); + } + + /** + * Get the long description for this composite + * command. + * + * @return string + */ + public function get_longdesc() { + return $this->longdesc . $this->get_global_params(); + } + + /** + * Set the long description for this composite command + * + * @param string + */ + public function set_longdesc( $longdesc ) { + $this->longdesc = Utils\normalize_eols( $longdesc ); + } + + /** + * Get the synopsis for this composite command. + * As a collection of subcommands, the composite + * command is only intended to invoke those + * subcommands. + * + * @return string + */ + public function get_synopsis() { + return ''; + } + + /** + * Get the usage for this composite command. + * + * @return string + */ + public function get_usage( $prefix ) { + return sprintf( + '%s%s %s', + $prefix, + implode( ' ', get_path( $this ) ), + $this->get_synopsis() + ); + } + + /** + * Show the usage for all subcommands contained + * by the composite command. + */ + public function show_usage() { + $methods = $this->get_subcommands(); + + $i = 0; + + foreach ( $methods as $name => $subcommand ) { + $prefix = ( 0 == $i++ ) ? 'usage: ' : ' or: '; + + if ( \EE::get_runner()->is_command_disabled( $subcommand ) ) { + continue; + } + + \EE::line( $subcommand->get_usage( $prefix ) ); + } + + $cmd_name = implode( ' ', array_slice( get_path( $this ), 1 ) ); + + \EE::line(); + \EE::line( "See 'ee help $cmd_name ' for more information on a specific command." ); + } + + /** + * When a composite command is invoked, it shows usage + * docs for its subcommands. + * + * @param array $args + * @param array $assoc_args + * @param array $extra_args + */ + public function invoke( $args, $assoc_args, $extra_args ) { + $this->show_usage(); + } + + /** + * Given supplied arguments, find a contained + * subcommand + * + * @param array $args + * @return \EE\Dispatcher\Subcommand|false + */ + public function find_subcommand( &$args ) { + $name = array_shift( $args ); + + $subcommands = $this->get_subcommands(); + + if ( ! isset( $subcommands[ $name ] ) ) { + $aliases = self::get_aliases( $subcommands ); + + if ( isset( $aliases[ $name ] ) ) { + $name = $aliases[ $name ]; + } + } + + if ( ! isset( $subcommands[ $name ] ) ) { + return false; + } + + return $subcommands[ $name ]; + } + + /** + * Get any registered aliases for this composite command's + * subcommands. + * + * @param array $subcommands + * @return array + */ + private static function get_aliases( $subcommands ) { + $aliases = array(); + + foreach ( $subcommands as $name => $subcommand ) { + $alias = $subcommand->get_alias(); + if ( $alias ) { + $aliases[ $alias ] = $name; + } + } + + return $aliases; + } + + /** + * Composite commands can only be known by one name. + * + * @return false + */ + public function get_alias() { + return false; + } + + /*** + * Get the list of global parameters + * + * @param string $root_command whether to include or not root command specific description + * @return string + */ + protected function get_global_params( $root_command = false ) { + $binding = array(); + $binding['root_command'] = $root_command; + + if ( ! $this->can_have_subcommands() || ( is_object( $this->parent ) && get_class( $this->parent ) == 'EE\Dispatcher\CompositeCommand' ) ) { + $binding['is_subcommand'] = true; + } + + foreach ( \EE::get_configurator()->get_spec() as $key => $details ) { + if ( false === $details['runtime'] ) { + continue; + } + + if ( isset( $details['deprecated'] ) ) { + continue; + } + + if ( isset( $details['hidden'] ) ) { + continue; + } + + if ( true === $details['runtime'] ) { + $synopsis = "--[no-]$key"; + } else { + $synopsis = "--$key" . $details['runtime']; + } + + $binding['parameters'][] = array( + 'synopsis' => $synopsis, + 'desc' => $details['desc'], + ); + } + + if ( $this->get_subcommands() ) { + $binding['has_subcommands'] = true; + } + + return Utils\mustache_render( 'man-params.mustache', $binding ); + } +} + diff --git a/php/EE/Dispatcher/RootCommand.php b/php/EE/Dispatcher/RootCommand.php new file mode 100644 index 000000000..204d6a552 --- /dev/null +++ b/php/EE/Dispatcher/RootCommand.php @@ -0,0 +1,59 @@ +parent = false; + + $this->name = 'ee'; + + $this->shortdesc = 'Manage EasyEngine through the command-line.'; + } + + /** + * Get the human-readable long description. + * + * @return string + */ + public function get_longdesc() { + return $this->get_global_params( true ); + } + + /** + * Find a subcommand registered on the root + * command. + * + * @param array $args + * @return \EE\Dispatcher\Subcommand|false + */ + public function find_subcommand( &$args ) { + $command = array_shift( $args ); + + Utils\load_command( $command ); + + if ( ! isset( $this->subcommands[ $command ] ) ) { + return false; + } + + return $this->subcommands[ $command ]; + } + + /** + * Get all registered subcommands. + * + * @return array + */ + public function get_subcommands() { + return parent::get_subcommands(); + } +} + diff --git a/php/EE/Dispatcher/Subcommand.php b/php/EE/Dispatcher/Subcommand.php new file mode 100644 index 000000000..520be2cb4 --- /dev/null +++ b/php/EE/Dispatcher/Subcommand.php @@ -0,0 +1,298 @@ +when_invoked = $when_invoked; + + $this->alias = $docparser->get_tag( 'alias' ); + + $this->synopsis = $docparser->get_synopsis(); + if ( ! $this->synopsis && $this->longdesc ) { + $this->synopsis = self::extract_synopsis( $this->longdesc ); + } + } + + /** + * Extract the synopsis from PHPdoc string. + * + * @param string $longdesc Command docs via PHPdoc + * @return string + */ + private static function extract_synopsis( $longdesc ) { + preg_match_all( '/(.+?)[\r\n]+:/', $longdesc, $matches ); + return implode( ' ', $matches[1] ); + } + + /** + * Subcommands can't have subcommands because they + * represent code to be executed. + * + * @return bool + */ + public function can_have_subcommands() { + return false; + } + + /** + * Get the synopsis string for this subcommand. + * A synopsis defines what runtime arguments are + * expected, useful to humans and argument validation. + * + * @return string + */ + public function get_synopsis() { + return $this->synopsis; + } + + /** + * Set the synopsis string for this subcommand. + * + * @param string + */ + public function set_synopsis( $synopsis ) { + $this->synopsis = $synopsis; + } + + /** + * If an alias is set, grant access to it. + * Aliases permit subcommands to be instantiated + * with a secondary identity. + * + * @return string + */ + public function get_alias() { + return $this->alias; + } + + /** + * Print the usage details to the end user. + * + * @param string $prefix + */ + public function show_usage( $prefix = 'usage: ' ) { + \EE::line( $this->get_usage( $prefix ) ); + } + + /** + * Get the usage of the subcommand as a formatted string. + * + * @param string $prefix + * @return string + */ + public function get_usage( $prefix ) { + return sprintf( + '%s%s %s', + $prefix, + implode( ' ', get_path( $this ) ), + $this->get_synopsis() + ); + } + + /** + * Validate the supplied arguments to the command. + * Throws warnings or errors if arguments are missing + * or invalid. + * + * @param array $args + * @param array $assoc_args + * @param array $extra_args + * @return array list of invalid $assoc_args keys to unset + */ + private function validate_args( $args, $assoc_args, $extra_args ) { + $synopsis = $this->get_synopsis(); + if ( ! $synopsis ) { + return array( array(), $args, $assoc_args, $extra_args ); + } + + $validator = new \EE\SynopsisValidator( $synopsis ); + + $cmd_path = implode( ' ', get_path( $this ) ); + foreach ( $validator->get_unknown() as $token ) { + \EE::warning( + sprintf( + 'The `%s` command has an invalid synopsis part: %s', + $cmd_path, + $token + ) + ); + } + + if ( ! $validator->enough_positionals( $args ) ) { + $this->show_usage(); + exit( 1 ); + } + + $unknown_positionals = $validator->unknown_positionals( $args ); + if ( ! empty( $unknown_positionals ) && 'wp' !== $this->name ) { + \EE::error( + 'Too many positional arguments: ' . + implode( ' ', $unknown_positionals ) + ); + } + + $synopsis_spec = \EE\SynopsisParser::parse( $synopsis ); + $i = 0; + $errors = array( + 'fatal' => array(), + 'warning' => array(), + ); + $mock_doc = array( $this->get_shortdesc(), '' ); + $mock_doc = array_merge( $mock_doc, explode( "\n", $this->get_longdesc() ) ); + $mock_doc = '/**' . PHP_EOL . '* ' . implode( PHP_EOL . '* ', $mock_doc ) . PHP_EOL . '*/'; + $docparser = new \EE\DocParser( $mock_doc ); + foreach ( $synopsis_spec as $spec ) { + if ( 'positional' === $spec['type'] ) { + $spec_args = $docparser->get_arg_args( $spec['name'] ); + if ( ! isset( $args[ $i ] ) ) { + if ( isset( $spec_args['default'] ) ) { + $args[ $i ] = $spec_args['default']; + } + } + if ( isset( $spec_args['options'] ) ) { + if ( ! empty( $spec['repeating'] ) ) { + do { + if ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { + \EE::error( 'Invalid value specified for positional arg.' ); + } + $i++; + } while ( isset( $args[ $i ] ) ); + } else { + if ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { + \EE::error( 'Invalid value specified for positional arg.' ); + } + } + } + $i++; + } elseif ( 'assoc' === $spec['type'] ) { + $spec_args = $docparser->get_param_args( $spec['name'] ); + if ( ! isset( $assoc_args[ $spec['name'] ] ) && ! isset( $extra_args[ $spec['name'] ] ) ) { + if ( isset( $spec_args['default'] ) ) { + $assoc_args[ $spec['name'] ] = $spec_args['default']; + } + } + if ( isset( $assoc_args[ $spec['name'] ] ) && isset( $spec_args['options'] ) ) { + if ( ! in_array( $assoc_args[ $spec['name'] ], $spec_args['options'] ) ) { + $errors['fatal'][ $spec['name'] ] = "Invalid value specified for '{$spec['name']}'"; + } + } + } + } + + list( $returned_errors, $to_unset ) = $validator->validate_assoc( + array_merge( \EE::get_config(), $extra_args, $assoc_args ) + ); + foreach ( array( 'fatal', 'warning' ) as $error_type ) { + $errors[ $error_type ] = array_merge( $errors[ $error_type ], $returned_errors[ $error_type ] ); + } + + if ( 'help' !== $this->name ) { + foreach ( $validator->unknown_assoc( $assoc_args ) as $key ) { + $suggestion = Utils\get_suggestion( + $key, + $this->get_parameters( $synopsis_spec ), + $threshold = 2 + ); + + $errors['fatal'][] = sprintf( + 'unknown --%s parameter%s', + $key, + ! empty( $suggestion ) ? PHP_EOL . "Did you mean '--{$suggestion}'?" : '' + ); + } + } + + if ( ! empty( $errors['fatal'] ) && 'wp' !== $this->name ) { + $out = 'Parameter errors:'; + foreach ( $errors['fatal'] as $key => $error ) { + $out .= "\n {$error}"; + if ( $desc = $docparser->get_param_desc( $key ) ) { + $out .= " ({$desc})"; + } + } + + \EE::error( $out ); + } + + array_map( '\\EE::warning', $errors['warning'] ); + + return array( $to_unset, $args, $assoc_args, $extra_args ); + } + + /** + * Invoke the subcommand with the supplied arguments. + * + * @param array $args + * @param array $assoc_args + */ + public function invoke( $args, $assoc_args, $extra_args ) { + + $extra_positionals = array(); + foreach ( $extra_args as $k => $v ) { + if ( is_numeric( $k ) ) { + if ( ! isset( $args[ $k ] ) ) { + $extra_positionals[ $k ] = $v; + } + unset( $extra_args[ $k ] ); + } + } + $args += $extra_positionals; + + list( $to_unset, $args, $assoc_args, $extra_args ) = $this->validate_args( $args, $assoc_args, $extra_args ); + + foreach ( $to_unset as $key ) { + unset( $assoc_args[ $key ] ); + } + + $path = get_path( $this->get_parent() ); + $parent = implode( ' ', array_slice( $path, 1 ) ); + $cmd = $this->name; + if ( $parent ) { + EE::do_hook( "before_invoke:{$parent}", $args, $assoc_args ); + $cmd = $parent . ' ' . $cmd; + } + EE::do_hook( "before_invoke:{$cmd}", $args, $assoc_args ); + + call_user_func( $this->when_invoked, $args, array_merge( $extra_args, $assoc_args ) ); + + if ( $parent ) { + EE::do_hook( "after_invoke:{$parent}" ); + } + EE::do_hook( "after_invoke:{$cmd}" ); + } + + /** + * Get an array of parameter names, by merging the command-specific and the + * global parameters. + * + * @param array $spec Optional. Specification of the current command. + * + * @return array Array of parameter names + */ + private function get_parameters( $spec = array() ) { + $local_parameters = array_column( $spec, 'name' ); + $global_parameters = array_column( + EE\SynopsisParser::parse( $this->get_global_params() ), + 'name' + ); + + return array_unique( array_merge( $local_parameters, $global_parameters ) ); + } +} + diff --git a/php/EE/DocParser.php b/php/EE/DocParser.php new file mode 100644 index 000000000..46e94fb24 --- /dev/null +++ b/php/EE/DocParser.php @@ -0,0 +1,208 @@ +docComment = self::remove_decorations( $docComment ); + } + + /** + * Remove unused cruft from PHPdoc comment. + * + * @param string $comment PHPdoc comment. + * @return string + */ + private static function remove_decorations( $comment ) { + $comment = preg_replace( '|^/\*\*[\r\n]+|', '', $comment ); + $comment = preg_replace( '|\n[\t ]*\*/$|', '', $comment ); + $comment = preg_replace( '|^[\t ]*\* ?|m', '', $comment ); + + return $comment; + } + + /** + * Get the command's short description (e.g. summary). + * + * @return string + */ + public function get_shortdesc() { + if ( ! preg_match( '|^([^@][^\n]+)\n*|', $this->docComment, $matches ) ) { + return ''; + } + + return $matches[1]; + } + + /** + * Get the command's full description + * + * @return string + */ + public function get_longdesc() { + $shortdesc = $this->get_shortdesc(); + if ( ! $shortdesc ) { + return ''; + } + + $longdesc = substr( $this->docComment, strlen( $shortdesc ) ); + + $lines = array(); + foreach ( explode( "\n", $longdesc ) as $line ) { + if ( 0 === strpos( $line, '@' ) ) { + break; + } + + $lines[] = $line; + } + $longdesc = trim( implode( "\n", $lines ) ); + + return $longdesc; + } + + /** + * Get the value for a given tag (e.g. "@alias" or "@subcommand") + * + * @param string $name Name for the tag, without '@' + * @return string + */ + public function get_tag( $name ) { + if ( preg_match( '|^@' . $name . '\s+([a-z-_0-9]+)|m', $this->docComment, $matches ) ) { + return $matches[1]; + } + + return ''; + } + + /** + * Check if a given tag is present (e.g. "@alias" or "@subcommand") + * + * @param string $name Name for the tag, without '@'. + * + * @return bool + */ + public function has_tag( $name ) { + return preg_match( '|^@' . $name . '|m', $this->docComment, $matches ); + } + + /** + * Get the command's synopsis. + * + * @return string + */ + public function get_synopsis() { + if ( ! preg_match( '|^@synopsis\s+(.+)|m', $this->docComment, $matches ) ) { + return ''; + } + + return $matches[1]; + } + + /** + * Get the description for a given argument. + * + * @param string $name Argument's doc name. + * @return string + */ + public function get_arg_desc( $name ) { + + if ( preg_match( "/\[?<{$name}>.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { + return $matches[1]; + } + + return ''; + + } + + /** + * Get the arguments for a given argument. + * + * @param string $name Argument's doc name. + * @return mixed|null + */ + public function get_arg_args( $name ) { + return $this->get_arg_or_param_args( "/^\[?<{$name}>.*/" ); + } + + /** + * Get the description for a given parameter. + * + * @param string $key Parameter's key. + * @return string + */ + public function get_param_desc( $key ) { + + if ( preg_match( "/\[?--{$key}=.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { + return $matches[1]; + } + + return ''; + } + + /** + * Get the arguments for a given parameter. + * + * @param string $key Parameter's key. + * @return mixed|null + */ + public function get_param_args( $key ) { + return $this->get_arg_or_param_args( "/^\[?--{$key}\[?=.*/" ); + } + + /** + * Get the args for an arg or param + * + * @param string $regex Pattern to match against + * @return array|null Interpreted YAML document, or null. + */ + private function get_arg_or_param_args( $regex ) { + $bits = explode( "\n", $this->docComment ); + $within_arg = $within_doc = false; + $document = array(); + foreach ( $bits as $bit ) { + if ( preg_match( $regex, $bit ) ) { + $within_arg = true; + } + + if ( $within_arg && $within_doc && '---' === $bit ) { + $within_doc = false; + } + + if ( $within_arg && ! $within_doc && '---' === $bit ) { + $within_doc = true; + } + + if ( $within_doc ) { + $document[] = $bit; + } + + if ( $within_arg && '' === $bit ) { + $within_arg = false; + break; + } + } + + if ( $document ) { + return Spyc::YAMLLoadString( implode( "\n", $document ) ); + } + return null; + } + +} diff --git a/php/EE/ExitException.php b/php/EE/ExitException.php new file mode 100644 index 000000000..d56dc4f3e --- /dev/null +++ b/php/EE/ExitException.php @@ -0,0 +1,5 @@ +open( $zipfile ); + if ( true === $res ) { + $tempdir = implode( + DIRECTORY_SEPARATOR, + array( + dirname( $zipfile ), + Utils\basename( $zipfile, '.zip' ), + $zip->getNameIndex( 0 ), + ) + ); + + $zip->extractTo( dirname( $tempdir ) ); + $zip->close(); + + self::copy_overwrite_files( $tempdir, $dest ); + self::rmdir( dirname( $tempdir ) ); + } else { + throw new \Exception( sprintf( "ZipArchive failed to unzip '%s': %s.", $zipfile, self::zip_error_msg( $res ) ) ); + } + } + + /** + * Extract a tarball to a specific destination. + * + * @param string $tarball + * @param string $dest + */ + private static function extract_tarball( $tarball, $dest ) { + + if ( class_exists( 'PharData' ) ) { + try { + $phar = new PharData( $tarball ); + $tempdir = implode( + DIRECTORY_SEPARATOR, + array( + dirname( $tarball ), + Utils\basename( $tarball, '.tar.gz' ), + $phar->getFilename(), + ) + ); + + $phar->extractTo( dirname( $tempdir ), null, true ); + + self::copy_overwrite_files( $tempdir, $dest ); + + self::rmdir( dirname( $tempdir ) ); + return; + } catch ( \Exception $e ) { + EE::warning( "PharData failed, falling back to 'tar xz' (" . $e->getMessage() . ')' ); + // Fall through to trying `tar xz` below + } + } + // Note: directory must exist for tar --directory to work. + $cmd = Utils\esc_cmd( 'tar xz --strip-components=1 --directory=%s -f %s', $dest, $tarball ); + $process_run = EE::launch( $cmd, false /*exit_on_error*/, true /*return_detailed*/ ); + if ( 0 !== $process_run->return_code ) { + throw new \Exception( sprintf( 'Failed to execute `%s`: %s.', $cmd, self::tar_error_msg( $process_run ) ) ); + } + } + + /** + * Copy files from source directory to destination directory. Source directory must exist. + * + * @param string $source + * @param string $dest + */ + public static function copy_overwrite_files( $source, $dest ) { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $source, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::SELF_FIRST + ); + + $error = 0; + + if ( ! is_dir( $dest ) ) { + mkdir( $dest, 0777, true ); + } + + foreach ( $iterator as $item ) { + + $dest_path = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); + + if ( $item->isDir() ) { + if ( ! is_dir( $dest_path ) ) { + mkdir( $dest_path ); + } + } else { + if ( file_exists( $dest_path ) && is_writable( $dest_path ) ) { + copy( $item, $dest_path ); + } elseif ( ! file_exists( $dest_path ) ) { + copy( $item, $dest_path ); + } else { + $error = 1; + EE::warning( "Unable to copy '" . $iterator->getSubPathName() . "' to current directory." ); + } + } + } + + if ( $error ) { + throw new \Exception( 'There was an error overwriting existing files.' ); + } + } + + /** + * Delete all files and directories recursively from directory. Directory must exist. + * + * @param string $dir + */ + public static function rmdir( $dir ) { + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ( $files as $fileinfo ) { + $todo = $fileinfo->isDir() ? 'rmdir' : 'unlink'; + $todo( $fileinfo->getRealPath() ); + } + rmdir( $dir ); + } + + /** + * Return formatted ZipArchive error message from error code. + * + * @param int $error_code + * @return string + */ + public static function zip_error_msg( $error_code ) { + // From https://github.com/php/php-src/blob/php-5.3.0/ext/zip/php_zip.c#L2623-L2646 + static $zip_err_msgs = array( + ZipArchive::ER_OK => 'No error', + ZipArchive::ER_MULTIDISK => 'Multi-disk zip archives not supported', + ZipArchive::ER_RENAME => 'Renaming temporary file failed', + ZipArchive::ER_CLOSE => 'Closing zip archive failed', + ZipArchive::ER_SEEK => 'Seek error', + ZipArchive::ER_READ => 'Read error', + ZipArchive::ER_WRITE => 'Write error', + ZipArchive::ER_CRC => 'CRC error', + ZipArchive::ER_ZIPCLOSED => 'Containing zip archive was closed', + ZipArchive::ER_NOENT => 'No such file', + ZipArchive::ER_EXISTS => 'File already exists', + ZipArchive::ER_OPEN => 'Can\'t open file', + ZipArchive::ER_TMPOPEN => 'Failure to create temporary file', + ZipArchive::ER_ZLIB => 'Zlib error', + ZipArchive::ER_MEMORY => 'Malloc failure', + ZipArchive::ER_CHANGED => 'Entry has been changed', + ZipArchive::ER_COMPNOTSUPP => 'Compression method not supported', + ZipArchive::ER_EOF => 'Premature EOF', + ZipArchive::ER_INVAL => 'Invalid argument', + ZipArchive::ER_NOZIP => 'Not a zip archive', + ZipArchive::ER_INTERNAL => 'Internal error', + ZipArchive::ER_INCONS => 'Zip archive inconsistent', + ZipArchive::ER_REMOVE => 'Can\'t remove file', + ZipArchive::ER_DELETED => 'Entry has been deleted', + ); + + if ( isset( $zip_err_msgs[ $error_code ] ) ) { + return sprintf( '%s (%d)', $zip_err_msgs[ $error_code ], $error_code ); + } + return $error_code; + } + + /** + * Return formatted error message from ProcessRun of tar command. + * + * @param Processrun $process_run + * @return string + */ + public static function tar_error_msg( $process_run ) { + $stderr = trim( $process_run->stderr ); + if ( false !== ( $nl_pos = strpos( $stderr, "\n" ) ) ) { + $stderr = trim( substr( $stderr, 0, $nl_pos ) ); + } + if ( $stderr ) { + return sprintf( '%s (%d)', $stderr, $process_run->return_code ); + } + return $process_run->return_code; + } +} diff --git a/php/EE/Fetchers/Base.php b/php/EE/Fetchers/Base.php new file mode 100644 index 000000000..7bc845e13 --- /dev/null +++ b/php/EE/Fetchers/Base.php @@ -0,0 +1,56 @@ +get( $arg ); + + if ( ! $item ) { + \EE::error( sprintf( $this->msg, $arg ) ); + } + + return $item; + } + + /** + * @param array The raw CLI arguments + * @return array The list of found items + */ + public function get_many( $args ) { + $items = array(); + + foreach ( $args as $arg ) { + $item = $this->get( $arg ); + + if ( $item ) { + $items[] = $item; + } else { + \EE::warning( sprintf( $this->msg, $arg ) ); + } + } + + return $items; + } +} + diff --git a/php/EE/FileCache.php b/php/EE/FileCache.php new file mode 100644 index 000000000..b8e900eb4 --- /dev/null +++ b/php/EE/FileCache.php @@ -0,0 +1,333 @@ + + * Jordi Boggiano + */ + +namespace EE; + +use Symfony\Component\Finder\Finder; + +/** + * Reads/writes to a filesystem cache + */ +class FileCache { + + /** + * @var string cache path + */ + protected $root; + /** + * @var bool + */ + protected $enabled = true; + /** + * @var int files time to live + */ + protected $ttl; + /** + * @var int max total size + */ + protected $maxSize; + /** + * @var string key allowed chars (regex class) + */ + protected $whitelist; + + /** + * @param string $cacheDir location of the cache + * @param int $ttl cache files default time to live (expiration) + * @param int $maxSize max total cache size + * @param string $whitelist List of characters that are allowed in path names (used in a regex character class) + */ + public function __construct( $cacheDir, $ttl, $maxSize, $whitelist = 'a-z0-9._-' ) { + $this->root = Utils\trailingslashit( $cacheDir ); + $this->ttl = (int) $ttl; + $this->maxSize = (int) $maxSize; + $this->whitelist = $whitelist; + + if ( ! $this->ensure_dir_exists( $this->root ) ) { + $this->enabled = false; + } + + } + + /** + * Cache is enabled + * + * @return bool + */ + public function is_enabled() { + return $this->enabled; + } + + /** + * Cache root + * + * @return string + */ + public function get_root() { + return $this->root; + } + + + /** + * Check if a file is in cache and return its filename + * + * @param string $key cache key + * @param int $ttl time to live + * @return bool|string filename or false + */ + public function has( $key, $ttl = null ) { + if ( ! $this->enabled ) { + return false; + } + + $filename = $this->filename( $key ); + + if ( ! file_exists( $filename ) ) { + return false; + } + + // use ttl param or global ttl + if ( null === $ttl ) { + $ttl = $this->ttl; + } elseif ( $this->ttl > 0 ) { + $ttl = min( (int) $ttl, $this->ttl ); + } else { + $ttl = (int) $ttl; + } + + // + if ( $ttl > 0 && filemtime( $filename ) + $ttl < time() ) { + if ( $this->ttl > 0 && $ttl >= $this->ttl ) { + unlink( $filename ); + } + return false; + } + + return $filename; + } + + /** + * Write to cache file + * + * @param string $key cache key + * @param string $contents file contents + * @return bool + */ + public function write( $key, $contents ) { + $filename = $this->prepare_write( $key ); + + if ( $filename ) { + return file_put_contents( $filename, $contents ) && touch( $filename ); + } + + return false; + } + + /** + * Read from cache file + * + * @param string $key cache key + * @param int $ttl time to live + * @return bool|string file contents or false + */ + public function read( $key, $ttl = null ) { + $filename = $this->has( $key, $ttl ); + + if ( $filename ) { + return file_get_contents( $filename ); + } + + return false; + } + + /** + * Copy a file into the cache + * + * @param string $key cache key + * @param string $source source filename + * @return bool + */ + public function import( $key, $source ) { + $filename = $this->prepare_write( $key ); + + if ( $filename ) { + return copy( $source, $filename ) && touch( $filename ); + } + + return false; + } + + /** + * Copy a file out of the cache + * + * @param string $key cache key + * @param string $target target filename + * @param int $ttl time to live + * @return bool + */ + public function export( $key, $target, $ttl = null ) { + $filename = $this->has( $key, $ttl ); + + if ( $filename ) { + return copy( $filename, $target ); + } + + return false; + } + + /** + * Remove file from cache + * + * @param string $key cache key + * @return bool + */ + public function remove( $key ) { + if ( ! $this->enabled ) { + return false; + } + + $filename = $this->filename( $key ); + + if ( file_exists( $filename ) ) { + return unlink( $filename ); + } + + return false; + } + + /** + * Clean cache based on time to live and max size + * + * @return bool + */ + public function clean() { + if ( ! $this->enabled ) { + return false; + } + + $ttl = $this->ttl; + $maxSize = $this->maxSize; + + // unlink expired files + if ( $ttl > 0 ) { + try { + $expire = new \DateTime(); + } catch ( \Exception $e ) { + \EE::error( $e->getMessage() ); + } + $expire->modify( '-' . $ttl . ' seconds' ); + + $finder = $this->get_finder()->date( 'until ' . $expire->format( 'Y-m-d H:i:s' ) ); + foreach ( $finder as $file ) { + unlink( $file->getRealPath() ); + } + } + + // unlink older files if max cache size is exceeded + if ( $maxSize > 0 ) { + $files = array_reverse( iterator_to_array( $this->get_finder()->sortByAccessedTime()->getIterator() ) ); + $total = 0; + + foreach ( $files as $file ) { + if ( $total + $file->getSize() <= $maxSize ) { + $total += $file->getSize(); + } else { + unlink( $file->getRealPath() ); + } + } + } + + return true; + } + + /** + * Ensure directory exists + * + * @param string $dir directory + * @return bool + */ + protected function ensure_dir_exists( $dir ) { + if ( ! is_dir( $dir ) ) { + if ( ! @mkdir( $dir, 0777, true ) ) { // @codingStandardsIgnoreLine + $error = error_get_last(); + \EE::warning( sprintf( "Failed to create directory '%s': %s.", $dir, $error['message'] ) ); + return false; + } + } + + return true; + } + + /** + * Prepare cache write + * + * @param string $key cache key + * @return bool|string filename or false + */ + protected function prepare_write( $key ) { + if ( ! $this->enabled ) { + return false; + } + + $filename = $this->filename( $key ); + + if ( ! $this->ensure_dir_exists( dirname( $filename ) ) ) { + return false; + } + + return $filename; + } + + /** + * Validate cache key + * + * @param string $key cache key + * @return string relative filename + */ + protected function validate_key( $key ) { + $url_parts = parse_url( $key ); + if ( ! empty( $url_parts['scheme'] ) ) { // is url + $parts = array( 'misc' ); + $parts[] = $url_parts['scheme'] . '-' . $url_parts['host'] . + ( empty( $url_parts['port'] ) ? '' : '-' . $url_parts['port'] ); + $parts[] = substr( $url_parts['path'], 1 ) . + ( empty( $url_parts['query'] ) ? '' : '-' . $url_parts['query'] ); + } else { + $key = str_replace( '\\', '/', $key ); + $parts = explode( '/', ltrim( $key ) ); + } + + $parts = preg_replace( "#[^{$this->whitelist}]#i", '-', $parts ); + + return implode( '/', $parts ); + } + + /** + * Filename from key + * + * @param string $key + * @return string filename + */ + protected function filename( $key ) { + return $this->root . $this->validate_key( $key ); + } + + /** + * Get a Finder that iterates in cache root only the files + * + * @return Finder + */ + protected function get_finder() { + return Finder::create()->in( $this->root )->files(); + } +} diff --git a/php/EE/Formatter.php b/php/EE/Formatter.php new file mode 100644 index 000000000..cee1e4857 --- /dev/null +++ b/php/EE/Formatter.php @@ -0,0 +1,361 @@ + 'table', + 'fields' => $fields, + 'field' => null, + ); + + foreach ( array( 'format', 'fields', 'field' ) as $key ) { + if ( isset( $assoc_args[ $key ] ) ) { + $format_args[ $key ] = $assoc_args[ $key ]; + unset( $assoc_args[ $key ] ); + } + } + + if ( ! is_array( $format_args['fields'] ) ) { + $format_args['fields'] = explode( ',', $format_args['fields'] ); + } + + $format_args['fields'] = array_map( 'trim', $format_args['fields'] ); + + $this->args = $format_args; + $this->prefix = $prefix; + } + + /** + * Magic getter for arguments. + * + * @param string $key + * @return mixed + */ + public function __get( $key ) { + return $this->args[ $key ]; + } + + /** + * Display multiple items according to the output arguments. + * + * @param array $items + * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `format()` if items in the table are pre-colorized. Default false. + */ + public function display_items( $items, $ascii_pre_colorized = false ) { + if ( $this->args['field'] ) { + $this->show_single_field( $items, $this->args['field'] ); + } else { + if ( in_array( $this->args['format'], array( 'csv', 'json', 'table' ) ) ) { + $item = is_array( $items ) && ! empty( $items ) ? array_shift( $items ) : false; + if ( $item && ! empty( $this->args['fields'] ) ) { + foreach ( $this->args['fields'] as &$field ) { + $field = $this->find_item_key( $item, $field ); + } + array_unshift( $items, $item ); + } + } + + if ( in_array( $this->args['format'], array( 'table', 'csv' ) ) ) { + if ( is_object( $items ) && is_a( $items, 'Iterator' ) ) { + $items = \EE\Utils\iterator_map( $items, array( $this, 'transform_item_values_to_json' ) ); + } else { + $items = array_map( array( $this, 'transform_item_values_to_json' ), $items ); + } + } + + $this->format( $items, $ascii_pre_colorized ); + } + } + + /** + * Display a single item according to the output arguments. + * + * @param mixed $item + * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_multiple_fields()` if the item in the table is pre-colorized. Default false. + */ + public function display_item( $item, $ascii_pre_colorized = false ) { + if ( isset( $this->args['field'] ) ) { + $item = (object) $item; + $key = $this->find_item_key( $item, $this->args['field'] ); + $value = $item->$key; + if ( in_array( $this->args['format'], array( 'table', 'csv' ) ) && ( is_object( $value ) || is_array( $value ) ) ) { + $value = json_encode( $value ); + } + \EE::print_value( + $value, + array( + 'format' => $this->args['format'], + ) + ); + } else { + $this->show_multiple_fields( $item, $this->args['format'], $ascii_pre_colorized ); + } + } + + /** + * Format items according to arguments. + * + * @param array $items + * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if items in the table are pre-colorized. Default false. + */ + private function format( $items, $ascii_pre_colorized = false ) { + $fields = $this->args['fields']; + + switch ( $this->args['format'] ) { + case 'count': + if ( ! is_array( $items ) ) { + $items = iterator_to_array( $items ); + } + echo count( $items ); + break; + + case 'ids': + if ( ! is_array( $items ) ) { + $items = iterator_to_array( $items ); + } + echo implode( ' ', $items ); + break; + + case 'table': + self::show_table( $items, $fields, $ascii_pre_colorized ); + break; + + case 'csv': + \EE\Utils\write_csv( STDOUT, $items, $fields ); + break; + + case 'json': + case 'yaml': + $out = array(); + foreach ( $items as $item ) { + $out[] = \EE\Utils\pick_fields( $item, $fields ); + } + + if ( 'json' === $this->args['format'] ) { + if ( defined( 'JSON_PARTIAL_OUTPUT_ON_ERROR' ) ) { + echo json_encode( $out, JSON_PRETTY_PRINT | JSON_PARTIAL_OUTPUT_ON_ERROR ); + } else { + echo json_encode( $out, JSON_PRETTY_PRINT ); + } + } elseif ( 'yaml' === $this->args['format'] ) { + echo Spyc::YAMLDump( $out, 2, 0 ); + } + break; + + default: + \EE::error( 'Invalid format: ' . $this->args['format'] ); + } + } + + /** + * Show a single field from a list of items. + * + * @param array Array of objects to show fields from + * @param string The field to show + */ + private function show_single_field( $items, $field ) { + $key = null; + $values = array(); + + foreach ( $items as $item ) { + $item = (object) $item; + + if ( null === $key ) { + $key = $this->find_item_key( $item, $field ); + } + + if ( 'json' == $this->args['format'] ) { + $values[] = $item->$key; + } else { + \EE::print_value( + $item->$key, + array( + 'format' => $this->args['format'], + ) + ); + } + } + + if ( 'json' == $this->args['format'] ) { + echo json_encode( $values, JSON_PRETTY_PRINT ); + } + } + + /** + * Find an object's key. + * If $prefix is set, a key with that prefix will be prioritized. + * + * @param object $item + * @param string $field + * @return string $key + */ + private function find_item_key( $item, $field ) { + foreach ( array( $field, $this->prefix . '_' . $field ) as $maybe_key ) { + if ( ( is_object( $item ) && ( property_exists( $item, $maybe_key ) || isset( $item->$maybe_key ) ) ) || ( is_array( $item ) && array_key_exists( $maybe_key, $item ) ) ) { + $key = $maybe_key; + break; + } + } + + if ( ! isset( $key ) ) { + \EE::error( "Invalid field: $field." ); + } + + return $key; + } + + /** + * Show multiple fields of an object. + * + * @param object|array $data Data to display + * @param string $format Format to display the data in + * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if the item in the table is pre-colorized. Default false. + */ + private function show_multiple_fields( $data, $format, $ascii_pre_colorized = false ) { + + $true_fields = array(); + foreach ( $this->args['fields'] as $field ) { + $true_fields[] = $this->find_item_key( $data, $field ); + } + + foreach ( $data as $key => $value ) { + if ( ! in_array( $key, $true_fields ) ) { + if ( is_array( $data ) ) { + unset( $data[ $key ] ); + } elseif ( is_object( $data ) ) { + unset( $data->$key ); + } + } + } + + switch ( $format ) { + + case 'table': + case 'csv': + $rows = $this->assoc_array_to_rows( $data ); + $fields = array( 'Field', 'Value' ); + if ( 'table' == $format ) { + self::show_table( $rows, $fields, $ascii_pre_colorized ); + } elseif ( 'csv' == $format ) { + \EE\Utils\write_csv( STDOUT, $rows, $fields ); + } + break; + + case 'yaml': + case 'json': + \EE::print_value( + $data, + array( + 'format' => $format, + ) + ); + break; + + default: + \EE::error( 'Invalid format: ' . $format ); + break; + + } + + } + + /** + * Show items in a \cli\Table. + * + * @param array $items + * @param array $fields + * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `Table::setAsciiPreColorized()` if items in the table are pre-colorized. Default false. + */ + private static function show_table( $items, $fields, $ascii_pre_colorized = false ) { + $table = new \cli\Table(); + + $enabled = \cli\Colors::shouldColorize(); + if ( $enabled ) { + \cli\Colors::disable( true ); + } + + $table->setAsciiPreColorized( $ascii_pre_colorized ); + $table->setHeaders( $fields ); + + foreach ( $items as $item ) { + $table->addRow( array_values( \EE\Utils\pick_fields( $item, $fields ) ) ); + } + + foreach ( $table->getDisplayLines() as $line ) { + \EE::line( $line ); + } + + if ( $enabled ) { + \cli\Colors::enable( true ); + } + } + + /** + * Format an associative array as a table. + * + * @param array $fields Fields and values to format + * @return array $rows + */ + private function assoc_array_to_rows( $fields ) { + $rows = array(); + + foreach ( $fields as $field => $value ) { + + if ( ! is_string( $value ) ) { + $value = json_encode( $value ); + } + + $rows[] = (object) array( + 'Field' => $field, + 'Value' => $value, + ); + } + + return $rows; + } + + /** + * Transforms objects and arrays to JSON as necessary + * + * @param mixed $item + * @return mixed + */ + public function transform_item_values_to_json( $item ) { + foreach ( $this->args['fields'] as $field ) { + $true_field = $this->find_item_key( $item, $field ); + $value = is_object( $item ) ? $item->$true_field : $item[ $true_field ]; + if ( is_array( $value ) || is_object( $value ) ) { + if ( is_object( $item ) ) { + $item->$true_field = json_encode( $value ); + } elseif ( is_array( $item ) ) { + $item[ $true_field ] = json_encode( $value ); + } + } + } + return $item; + } + +} diff --git a/php/EE/Iterators/CSV.php b/php/EE/Iterators/CSV.php new file mode 100644 index 000000000..d5a42ab7a --- /dev/null +++ b/php/EE/Iterators/CSV.php @@ -0,0 +1,78 @@ +filePointer = fopen( $filename, 'rb' ); + if ( ! $this->filePointer ) { + \EE::error( sprintf( 'Could not open file: %s', $filename ) ); + } + + $this->delimiter = $delimiter; + } + + public function rewind() { + rewind( $this->filePointer ); + + $this->columns = fgetcsv( $this->filePointer, self::ROW_SIZE, $this->delimiter ); + + $this->currentIndex = -1; + $this->next(); + } + + public function current() { + return $this->currentElement; + } + + public function key() { + return $this->currentIndex; + } + + public function next() { + $this->currentElement = false; + + while ( true ) { + $str = fgets( $this->filePointer ); + + if ( false === $str ) { + break; + } + + $row = str_getcsv( $str, $this->delimiter ); + + $element = array(); + foreach ( $this->columns as $i => $key ) { + if ( isset( $row[ $i ] ) ) { + $element[ $key ] = $row[ $i ]; + } + } + + if ( ! empty( $element ) ) { + $this->currentElement = $element; + $this->currentIndex++; + + break; + } + } + } + + public function valid() { + return is_array( $this->currentElement ); + } +} + diff --git a/php/EE/Iterators/Exception.php b/php/EE/Iterators/Exception.php new file mode 100644 index 000000000..3469b683c --- /dev/null +++ b/php/EE/Iterators/Exception.php @@ -0,0 +1,6 @@ +transformers[] = $fn; + } + + public function current() { + $value = parent::current(); + + foreach ( $this->transformers as $fn ) { + $value = call_user_func( $fn, $value ); + } + + return $value; + } +} + diff --git a/php/EE/Loggers/Base.php b/php/EE/Loggers/Base.php new file mode 100644 index 000000000..f213a5cff --- /dev/null +++ b/php/EE/Loggers/Base.php @@ -0,0 +1,81 @@ +get_runner()->config['debug']; + if ( ! $debug ) { + return; + } + if ( true !== $debug && $group !== $debug ) { + return; + } + $time = round( microtime( true ) - ( defined( 'EE_START_MICROTIME' ) ? EE_START_MICROTIME : $start_time ), 3 ); + $prefix = 'Debug'; + if ( $group && true === $debug ) { + $prefix = 'Debug (' . $group . ')'; + } + $this->_line( "$message ({$time}s)", $prefix, '%B', STDERR ); + } + + /** + * Write a string to a resource. + * + * @param resource $handle Commonly STDOUT or STDERR. + * @param string $str Message to write. + */ + protected function write( $handle, $str ) { + fwrite( $handle, $str ); + } + + /** + * Output one line of message to a resource. + * + * @param string $message Message to write. + * @param string $label Prefix message with a label. + * @param string $color Colorize label with a given color. + * @param resource $handle Resource to write to. Defaults to STDOUT. + */ + protected function _line( $message, $label, $color, $handle = STDOUT ) { + if ( class_exists( 'cli\Colors' ) ) { + $label = \cli\Colors::colorize( "$color$label:%n", $this->in_color ); + } else { + $label = "$label:"; + } + $this->write( $handle, "$label $message\n" ); + } + +} diff --git a/php/EE/Loggers/Execution.php b/php/EE/Loggers/Execution.php new file mode 100644 index 000000000..2ee7aa0cc --- /dev/null +++ b/php/EE/Loggers/Execution.php @@ -0,0 +1,80 @@ +write( STDERR, \EE::colorize( "%RError:%n\n$message\n" ) ); + $this->write( STDERR, \EE::colorize( "%R---------%n\n\n" ) ); + } + + /** + * Write a string to a resource. + * + * @param resource $handle Commonly STDOUT or STDERR. + * @param string $str Message to write. + */ + protected function write( $handle, $str ) { + switch ( $handle ) { + case STDOUT: + $this->stdout .= $str; + break; + case STDERR: + $this->stderr .= $str; + break; + } + } + + /** + * Starts output buffering, using a callback to capture output from `echo`, `print`, `printf` (which write to the output buffer 'php://output' rather than STDOUT). + */ + public function ob_start() { + ob_start( array( $this, 'ob_start_callback' ), 1 ); + } + + /** + * Callback for `ob_start()`. + * + * @param string $str String to write. + * @return string Returns zero-length string so nothing gets written to the output buffer. + */ + public function ob_start_callback( $str ) { + $this->write( STDOUT, $str ); + return ''; + } + + /** + * To match `ob_start() above. Does an `ob_end_flush()`. + */ + public function ob_end() { + ob_end_flush(); + } +} diff --git a/php/EE/Loggers/Quiet.php b/php/EE/Loggers/Quiet.php new file mode 100644 index 000000000..6195288d3 --- /dev/null +++ b/php/EE/Loggers/Quiet.php @@ -0,0 +1,57 @@ +write( STDERR, \EE::colorize( "%RError:%n $message\n" ) ); + } + + /** + * Similar to error( $message ), but outputs $message in a red box + * + * @param array $message Message to write. + */ + public function error_multi_line( $message_lines ) { + $message = implode( "\n", $message_lines ); + + $this->write( STDERR, \EE::colorize( "%RError:%n\n$message\n" ) ); + $this->write( STDERR, \EE::colorize( "%R---------%n\n\n" ) ); + } +} diff --git a/php/EE/Loggers/Regular.php b/php/EE/Loggers/Regular.php new file mode 100644 index 000000000..168e8fbc8 --- /dev/null +++ b/php/EE/Loggers/Regular.php @@ -0,0 +1,82 @@ +in_color = $in_color; + } + + /** + * Write an informational message to STDOUT. + * + * @param string $message Message to write. + */ + public function info( $message ) { + $this->write( STDOUT, $message . "\n" ); + } + + /** + * Write a success message, prefixed with "Success: ". + * + * @param string $message Message to write. + */ + public function success( $message ) { + $this->_line( $message, 'Success', '%G' ); + } + + /** + * Write a warning message to STDERR, prefixed with "Warning: ". + * + * @param string $message Message to write. + */ + public function warning( $message ) { + $this->_line( $message, 'Warning', '%C', STDERR ); + } + + /** + * Write an message to STDERR, prefixed with "Error: ". + * + * @param string $message Message to write. + */ + public function error( $message ) { + $this->_line( $message, 'Error', '%R', STDERR ); + } + + /** + * Similar to error( $message ), but outputs $message in a red box + * + * @param array $message Message to write. + */ + public function error_multi_line( $message_lines ) { + // convert tabs to four spaces, as some shells will output the tabs as variable-length + $message_lines = array_map( + function( $line ) { + return str_replace( "\t", ' ', $line ); + }, + $message_lines + ); + + $longest = max( array_map( 'strlen', $message_lines ) ); + + // write an empty line before the message + $empty_line = \cli\Colors::colorize( '%w%1 ' . str_repeat( ' ', $longest ) . ' %n' ); + $this->write( STDERR, "\n\t$empty_line\n" ); + + foreach ( $message_lines as $line ) { + $padding = str_repeat( ' ', $longest - strlen( $line ) ); + $line = \cli\Colors::colorize( "%w%1 $line $padding%n" ); + $this->write( STDERR, "\t$line\n" ); + } + + // write an empty line after the message + $this->write( STDERR, "\t$empty_line\n\n" ); + } +} diff --git a/php/EE/Migration/Base.php b/php/EE/Migration/Base.php new file mode 100644 index 000000000..5008036aa --- /dev/null +++ b/php/EE/Migration/Base.php @@ -0,0 +1,27 @@ +fs = new Filesystem(); + $this->skip_this_migration = false; + $this->is_first_execution = ! \EE\Model\Option::get( 'version' ); + $this->backup_dir = EE_BACKUP_DIR; + $this->fs->mkdir( $this->backup_dir ); + } + + abstract public function up(); + + abstract public function down(); +} diff --git a/php/EE/Migration/Containers.php b/php/EE/Migration/Containers.php new file mode 100644 index 000000000..b00b94918 --- /dev/null +++ b/php/EE/Migration/Containers.php @@ -0,0 +1,376 @@ + $version ) { + + if ( array_key_exists( $img, $current_versions ) ) { + if ( $current_versions[ $img ] !== $version ) { + $updated_images[] = $img; + if ( ! in_array( $img, $skip_download ) ) { + self::pull_or_error( $img, $version ); + } + } + } else { + if ( ! in_array( $img, $skip_download ) ) { + self::pull_or_error( $img, $version ); + } + } + } + + if ( empty( $updated_images ) ) { + + return; + } + + self::migrate_global_containers( $updated_images ); + self::migrate_site_containers( $updated_images ); + self::maybe_update_docker_compose(); + self::save_upgraded_image_versions( $current_versions, $img_versions, $updated_images ); + + if ( ! self::$rsp->execute() ) { + throw new \Exception( 'Unable to migrate sites to newer version' ); + } + + EE\Utils\delem_log( 'Container migration completed' ); + } + + /** + * Maybe update docker-compose at the end of migration. + * Need to update to latest docker-compose version for new template changes. + */ + public static function maybe_update_docker_compose() { + + self::$rsp->add_step( + 'update-compose', + 'EE\Migration\Containers::update_docker_compose', + 'EE\Migration\Containers::revert_docker_compose', + null, + null + ); + } + + /** + * Save updated image version in database. + * + * @param $current_versions array of current image versions. + * @param $new_versions array of new image version. + * @param $updated_images array of updated images. + */ + public static function save_upgraded_image_versions( $current_versions, $new_versions, $updated_images ) { + + self::$rsp->add_step( + 'save-image-verions-in-database', + 'EE\Migration\Containers::create_database_entry', + 'EE\Migration\Containers::revert_database_entry', + [ $new_versions, $updated_images ], + [ $current_versions, $updated_images ] + ); + + self::$rsp->add_step( + 'prune-old-docker-images', + 'EE\Migration\Containers::docker_cleanup', + null, + null, + null + ); + + } + + /** + * Prune old and extra EE Docker images. + */ + public static function docker_cleanup() { + EE::exec( 'docker image prune -af --filter=label=org.label-schema.vendor="EasyEngine"' ); + EE::exec( 'docker network prune -f --filter=label=org.label-schema.vendor="EasyEngine"' ); + } + + /** + * Update docker-compose to v2.27.0 if lower version is installed. + */ + public static function update_docker_compose() { + + $docker_compose_version = trim( EE::launch( 'docker-compose version --short' )->stdout ); + $docker_compose_path = trim( EE::launch( 'command -v docker-compose' )->stdout ); + $docker_compose_backup_path = EE_BACKUP_DIR . '/docker-compose.backup'; + $docker_compose_new_path = EE_BACKUP_DIR . '/docker-compose'; + + if ( version_compare( '2.27.0', $docker_compose_version, '>' ) ) { + + $fs = new Filesystem(); + if ( ! $fs->exists( EE_BACKUP_DIR ) ) { + $fs->mkdir( EE_BACKUP_DIR ); + } + $fs->copy( $docker_compose_path, $docker_compose_backup_path ); + + EE::exec( "curl -L https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-$(uname -s)-$(uname -m) -o $docker_compose_new_path && chmod +x $docker_compose_new_path" ); + EE::exec( "mv $docker_compose_new_path $docker_compose_path" ); + } + } + + /** + * Revert docker-compose to previous version. + */ + public static function revert_docker_compose() { + + $docker_compose_path = EE::launch( 'command -v docker-compose' )->stdout; + $docker_compose_path = trim( $docker_compose_path ); + $docker_compose_backup_path = EE_BACKUP_DIR . '/docker-compose.backup'; + $fs = new Filesystem(); + $fs->copy( $docker_compose_backup_path, $docker_compose_path ); + } + + /** + * Update database entry of images + * + * @param $new_versions array of new image versions. + * @param $updated_images array of updated images. + * + * @throws \Exception + */ + public static function create_database_entry( $new_versions, $updated_images ) { + foreach ( $updated_images as $image ) { + EE\Model\Option::update( [ [ 'key', $image ] ], [ 'value' => $new_versions[ $image ] ] ); + } + } + + /** + * Revert database entry in case of exception. + * + * @param $old_version array of old image versions. + * @param $updated_images array of updated images. + * + * @throws \Exception + */ + public static function revert_database_entry( $old_version, $updated_images ) { + foreach ( $updated_images as $image ) { + EE\Model\Option::update( [ [ 'key', $image ] ], [ 'value' => $old_version[ $image ] ] ); + } + } + + /** + * Helper method to ensure exception is thrown if image isn't pulled + * + * @param $image docker image to pull + * @param $version version of image to pull + * + * @throws \Exception + */ + public static function pull_or_error( $image, $version ) { + if ( ! \EE::exec( "docker pull $image:$version" ) ) { + throw new \Exception( "Unable to pull $image. Please check logs for more details." ); + } + } + + /** + * Get current docker images versions. + * + * @return array + * @throws \Exception + */ + public static function get_current_docker_images_versions() { + + $dbImages = EE::db() + ->table( 'options' ) + ->where( 'key', 'like', 'easyengine/%' ) + ->all(); + + $dbImages = array_column( $dbImages, 'value', 'key' ); + + $dockerImages = EE::launch( 'docker ps --format "{{.Image}}" | grep "^easyengine/" | sort -u' )->stdout; + $dockerImages = explode( "\n", trim( $dockerImages ) ); + + $dockerImages = array_reduce( $dockerImages, function ( $result, $image ) { + + [ $imageName, $tag ] = explode( ':', $image, 2 ) + [ 1 => null ]; + $result[ $imageName ] = $tag; + + return $result; + }, [] ); + + $mergedImages = $dockerImages + $dbImages; + $mergedImages = array_filter( $mergedImages ); + + return $mergedImages; + } + + /** + * Migrates global containers. These are container which are not created per site (i.e. ee-cron-scheduler). + * + * @param $updated_images array of updated images. + */ + private static function migrate_global_containers( $updated_images ) { + + $updated_global_images = GlobalContainers::get_updated_global_images( $updated_images ); + if ( empty( $updated_global_images ) ) { + return; + } + + $global_compose_file_path = EE_ROOT_DIR . '/services/docker-compose.yml'; + $global_compose_file_backup_path = EE_BACKUP_DIR . '/services/docker-compose.yml.backup'; + + self::$rsp->add_step( + 'backup-global-docker-compose-file', + 'EE\Migration\SiteContainers::backup_restore', + 'EE\Migration\GlobalContainers::revert_global_containers', + [ $global_compose_file_path, $global_compose_file_backup_path ], + [ $global_compose_file_backup_path, $global_compose_file_path, $updated_global_images ] + ); + + self::$rsp->add_step( + 'stop-global-containers', + 'EE\Migration\GlobalContainers::down_global_containers', + null, + [ $updated_global_images ], + null + ); + + self::$rsp->add_step( + 'generate-global-docker-compose-file', + 'EE\Service\Utils\generate_global_docker_compose_yml', + null, + [ new \Symfony\Component\Filesystem\Filesystem() ], + null + ); + + $all_global_images = GlobalContainers::get_all_global_images_with_service_name(); + foreach ( $updated_global_images as $image_name ) { + $global_container_name = $all_global_images[ $image_name ]; + $global_service_name = ltrim( $global_container_name, 'services_' ); + $global_service_name = rtrim( $global_service_name, '_1' ); + self::$rsp->add_step( + "upgrade-$global_container_name-container", + "EE\Migration\GlobalContainers::global_service_up", + "EE\Migration\GlobalContainers::global_service_down", + [ $global_service_name ], + [ $global_service_name ] + ); + } + } + + /** + * Migrate site specific container. + * + * @param $updated_images array of updated images + * + * @throws \Exception + */ + public static function migrate_site_containers( $updated_images ) { + + $db = new \EE_DB(); + $sites = ( $db->table( 'sites' )->all() ); + + foreach ( $sites as $site ) { + + $docker_yml = $site['site_fs_path'] . '/docker-compose.yml'; + $docker_yml_backup = EE_BACKUP_DIR . '/' . $site['site_url'] . '/docker-compose.yml.backup'; + + if ( ! SiteContainers::is_site_service_image_changed( $updated_images, $site ) ) { + continue; + } + + $ee_site_object = SiteContainers::get_site_object( $site['site_type'] ); + + if ( $site['site_enabled'] ) { + + /** + * Enable support containers. + */ + self::$rsp->add_step( + sprintf( 'enable-support-containers-%s', $site['site_url'] ), + 'EE\Migration\SiteContainers::enable_support_containers', + 'EE\Migration\SiteContainers::disable_support_containers', + [ $site['site_url'], $site['site_fs_path'] ], + [ $site['site_url'], $site['site_fs_path'] ] + ); + + self::$rsp->add_step( + "disable-${site['site_url']}-containers", + 'EE\Migration\SiteContainers::disable_default_containers', + 'EE\Migration\SiteContainers::enable_default_containers', + [ $site ], + [ $site, $ee_site_object ] + ); + } + + self::$rsp->add_step( + "take-${site['site_url']}-docker-compose-backup", + 'EE\Migration\SiteContainers::backup_restore', + 'EE\Migration\SiteContainers::backup_restore', + [ $docker_yml, $docker_yml_backup ], + [ $docker_yml_backup, $docker_yml ] + ); + + self::$rsp->add_step( + "generate-${site['site_url']}-docker-compose", + 'EE\Migration\SiteContainers::generate_site_docker_compose_file', + null, + [ $site, $ee_site_object ], + null + ); + + if ( $site['site_enabled'] ) { + self::$rsp->add_step( + "upgrade-${site['site_url']}-containers", + 'EE\Migration\SiteContainers::enable_default_containers', + 'EE\Migration\SiteContainers::enable_default_containers', + [ $site, $ee_site_object ], + [ $site, $ee_site_object ] + ); + + /** + * Disable support containers. + */ + self::$rsp->add_step( + sprintf( 'disable-support-containers-%s', $site['site_url'] ), + 'EE\Migration\SiteContainers::disable_support_containers', + 'EE\Migration\SiteContainers::enable_support_containers', + [ $site['site_url'], $site['site_fs_path'] ], + [ $site['site_url'], $site['site_fs_path'] ] + ); + } + } + } + +} diff --git a/php/EE/Migration/CustomContainerMigrations.php b/php/EE/Migration/CustomContainerMigrations.php new file mode 100644 index 000000000..39f5c86e5 --- /dev/null +++ b/php/EE/Migration/CustomContainerMigrations.php @@ -0,0 +1,230 @@ +getMessage(), false ); + throw $e; + } + + try { + EE::debug( "Migrating: $migrations[0]" ); + $migration->up(); + + Migration::create( [ + 'migration' => $migrations[0], + 'type' => 'container', + 'timestamp' => date( 'Y-m-d H:i:s' ), + ] ); + + $migration->status = 'complete'; + EE::debug( "Migrated: $migrations[0]" ); + $remaining_migrations = array_splice( $migrations, 1, count( $migrations ) ); + self::execute_migration_stack( $remaining_migrations ); + } catch ( \Throwable $e ) { + if ( 'complete' !== $migration->status ) { + EE::error( "Errors were encountered while processing: $migrations[0]\n" . $e->getMessage(), false ); + } + EE::debug( "Reverting: $migrations[0]" ); + // remove db entry in 'migration' table when reverting migrations. + $migrated = Migration::where( 'migration', $migrations[0] ); + if ( ! empty( $migrated ) ) { + $migration->down(); + $migrated[0]->delete(); + } + + EE::debug( "Reverted: $migrations[0]" ); + throw $e; + } + } + + /** + * Get migrations to be executed. + * + * @param $migrations array of all migrations. + * + * @return array + * @throws \Exception + */ + private static function get_migrations_to_execute( $migrations ) { + return array_values( + array_diff( + $migrations, + self::get_migrations_from_db() + ) + ); + } + + /** + * Get already migrated migrations from database. + * + * @return array|void + * @throws \Exception + */ + private static function get_migrations_from_db() { + return array_column( Migration::where( 'type', 'container' ), 'migration' ); + } + + /** + * Get path of the migration file. + * + * @param $migration_name name of a migration file. + * + * @return string path of a migration file. + */ + private static function get_migration_path( $migration_name ) { + preg_match( '/^\d*[_]([a-zA-Z-]*)[_]/', $migration_name, $matches ); + + if ( empty( $matches[1] ) ) { + return ''; + } + if ( 'easyengine' === $matches[1] ) { + return EE_ROOT . "/migrations/container/$migration_name"; + } else { + return EE_ROOT . "/vendor/easyengine/$matches[1]/migrations/container/$migration_name"; + } + + } + + /** + * Get class name from name of migration file. + * + * @param $migration_name string name of migration file. + * + * @return string + */ + private static function get_migration_class_name( $migration_name ) { + // Remove date and package name from it + $class_name = preg_replace( '/(^\d*)[_]([a-zA-Z-]*[_])/', '', substr( $migration_name, 0, -4 ) ); + // Convet snake_case to CamelCase + $class_name = self::camelize( $class_name ); + // Replace dot with underscore + $class_name = str_replace( '.', '_', $class_name ); + + return "\EE\Migration\\$class_name"; + } + + /** + * Convert string in camelcase format. + * + * @param $input string to be camelized. + * @param string $separator string of separator. + * + * @return mixed + */ + private static function camelize( $input, $separator = '_' ) { + return str_replace( $separator, '', ucwords( $input, $separator ) ); + } +} diff --git a/php/EE/Migration/Executor.php b/php/EE/Migration/Executor.php new file mode 100644 index 000000000..249e00df9 --- /dev/null +++ b/php/EE/Migration/Executor.php @@ -0,0 +1,209 @@ +getMessage(), false ); + throw $e; + } + + try { + EE::debug( "Migrating: $migrations[0]" ); + $migration->up(); + + Migration::create( [ + 'migration' => $migrations[0], + 'type' => 'db', + 'timestamp' => date( 'Y-m-d H:i:s' ), + ] ); + + $migration->status = 'complete'; + EE::debug( "Migrated: $migrations[0]" ); + $remaining_migrations = array_splice( $migrations, 1, count( $migrations ) ); + self::execute_migration_stack( $remaining_migrations ); + } catch ( \Throwable $e ) { + if ( 'complete' !== $migration->status ) { + EE::error( "Errors were encountered while processing: $migrations[0]\n" . $e->getMessage(), false ); + } + EE::debug( "Reverting: $migrations[0]" ); + // remove db entry in 'migration' table when reverting migrations. + $migrated = Migration::where( 'migration', $migrations[0] ); + if ( ! empty( $migrated ) ) { + $migration->down(); + $migrated[0]->delete(); + } + + EE::debug( "Reverted: $migrations[0]" ); + throw $e; + } + } + + /** + * Get migrations need to be executed. + * + * @param $path path to the migration directory. + * + * @return array + */ + private static function get_migrations_to_execute( $migrations ) { + return array_values( + array_diff( + $migrations, + self::get_migrations_from_db() + ) + ); + } + + /** + * Get already migrated migrations from database. + * + * @return array + */ + private static function get_migrations_from_db() { + return array_column( Migration::where( 'type', 'db' ), 'migration' ); + } + + /** + * Get path of the migration file. + * + * @param $migration_name name of a migration file. + * + * @return string path of a migration file. + */ + private static function get_migration_path( $migration_name ) { + preg_match( '/^\d*[_]([a-zA-Z-]*)[_]/', $migration_name, $matches ); + + if ( empty( $matches[1] ) ) { + return ''; + } + if ( 'easyengine' === $matches[1] ) { + return EE_ROOT . "/migrations/db/$migration_name"; + } else { + return EE_ROOT . "/vendor/easyengine/$matches[1]/migrations/db/$migration_name"; + } + + } + + private static function get_migration_class_name( $migration_name ) { + // Remove date and package name from it + $class_name = preg_replace( '/(^\d*)[_]([a-zA-Z-]*[_])/', '', rtrim( $migration_name, '.php' ) ); + // Convet snake_case to CamelCase + $class_name = self::camelize( $class_name ); + // Replace dot with underscore + $class_name = str_replace( '.', '_', $class_name ); + + return "\EE\Migration\\$class_name"; + } + + private static function camelize( $input, $separator = '_' ) { + return str_replace( $separator, '', ucwords( $input, $separator ) ); + } +} diff --git a/php/EE/Migration/GlobalContainers.php b/php/EE/Migration/GlobalContainers.php new file mode 100644 index 000000000..ca762bf54 --- /dev/null +++ b/php/EE/Migration/GlobalContainers.php @@ -0,0 +1,140 @@ + $container_name ) { + if ( 'running' === \EE_DOCKER::container_status( $container_name ) ) { + $running_global_services[] = $image; + } + } + + return array_intersect( $running_global_services, $updated_images ); + } + + /** + * * Restore backed up docker-compose.yml file. + * + * @param $source_path string path of backup file. + * @param $dest_path string path of global docker-compose.yml + * + * @throws \Exception + */ + public static function revert_global_containers( $source_path, $dest_path, $updated_images ) { + + $services_to_regenerate = ''; + $all_global_images = self::get_all_global_images_with_service_name(); + foreach ( $updated_images as $image_name ) { + $global_container_name = $all_global_images[ $image_name ]; + $services_to_regenerate .= ltrim( rtrim( $global_container_name, '_1' ), 'services_' ) . ' '; + } + if ( empty( trim( $services_to_regenerate ) ) ) { + return; + } + EE::debug( 'Start restoring global docker-compose.yml file from backup' ); + $fs = new Filesystem(); + $fs->copy( $source_path, $dest_path, true ); + + chdir( EE_ROOT_DIR . '/services' ); + + if ( ! EE::exec( \EE_DOCKER::docker_compose_with_custom() . ' up -d ' . $services_to_regenerate ) ) { + throw new \Exception( 'Unable to downgrade global containers. Please check logs for more details.' ); + } + EE::debug( 'Complete restoring global docker-compose.yml file from backup' ); + } + + /** + * Stop global container and remove them. + * + * @param $updated_images array of newly available images. + * + * @throws \Exception + */ + public static function down_global_containers( $updated_images ) { + EE::debug( 'Start removing global containers' ); + chdir( EE_ROOT_DIR . '/services' ); + $all_global_images = self::get_all_global_images_with_service_name(); + + foreach ( $updated_images as $image_name ) { + $global_container_name = $all_global_images[ $image_name ]; + $global_service_name = ltrim( $global_container_name, 'services_' ); + $remove_suffix = explode( '_1', $global_service_name ); + $global_service_name = empty( $remove_suffix[0] ) ? $global_service_name : $remove_suffix[0]; + EE::debug( "Removing $global_container_name" ); + + if ( false !== \EE_DOCKER::container_status( $global_container_name ) ) { + if ( ! EE::exec( \EE_DOCKER::docker_compose_with_custom() . " stop $global_service_name && ". \EE_DOCKER::docker_compose_with_custom() . " rm -f $global_service_name" ) ) { + throw new \Exception( "Unable to stop $global_container_name container" ); + } + } + } + EE::debug( 'Complete removing global containers' ); + } + + /** + * Upgrade global service container. + * + * @throws \Exception + */ + public static function global_service_up( $service_name ) { + $global_service_name = ltrim( $service_name, 'services_' ); + $remove_suffix = explode( '_1', $global_service_name ); + $global_service_name = empty( $remove_suffix[0] ) ? $global_service_name : $remove_suffix[0]; + EE::debug( 'Start ' . $service_name . ' container up' ); + if ( 'global-nginx-proxy' === $global_service_name ) { + \EE\Service\Utils\nginx_proxy_check(); + } else { + \EE\Service\Utils\init_global_container( $service_name ); + } + } + + /** + * Remove upgraded global service container. + * + * @throws \Exception + */ + public static function global_service_down( $service_name ) { + EE::debug( 'Start ' . $service_name . ' container removing' ); + chdir( EE_ROOT_DIR . '/services' ); + + if ( ! EE::exec( \EE_DOCKER::docker_compose_with_custom() . " stop $service_name && ". \EE_DOCKER::docker_compose_with_custom() . " rm -f $service_name" ) ) { + throw new \Exception( sprintf( 'Unable to remove %1$s container', $service_name ) ); + } + EE::debug( 'Complete ' . $service_name . ' container removing' ); + } + + + /** + * Get all global images with it's service name. + * + * @return array + */ + public static function get_all_global_images_with_service_name() { + + return [ + 'easyengine/nginx-proxy' => GLOBAL_PROXY_CONTAINER, + 'easyengine/mariadb' => GLOBAL_DB_CONTAINER, + 'easyengine/redis' => GLOBAL_REDIS_CONTAINER, + 'easyengine/cron' => GLOBAL_CRON_CONTAINER, + 'easyengine/newrelic-daemon' => GLOBAL_NEWRELIC_DAEMON_CONTAINER, + ]; + } +} diff --git a/php/EE/Migration/SiteContainers.php b/php/EE/Migration/SiteContainers.php new file mode 100644 index 000000000..f2b7e0e31 --- /dev/null +++ b/php/EE/Migration/SiteContainers.php @@ -0,0 +1,305 @@ +exists( $source_path ) ) { + throw new \Exception( ' site\'s docker-compose.yml does not exist' ); + } + $fs->copy( $source_path, $destination_path, true ); + EE::debug( 'Complete backing up site\'s docker-compose.yml' ); + } + + /** + * Revert docker-compose.yml file from backup. + * + * @param string $source_path path of backed up docker-compose.yml. + * @param string $destination_path original path of docker-compose.yml. + * + * @throws \Exception + */ + public static function revert_site_docker_compose_file( $source_path, $destination_path ) { + EE::debug( 'Start restoring site\'s docker-compose.yml' ); + $fs = new Filesystem(); + if ( ! $fs->exists( $source_path ) ) { + throw new \Exception( ' site\'s docker-compose.yml.backup does not exist' ); + } + $fs->copy( $source_path, $destination_path, true ); + $fs->remove( $source_path ); + EE::debug( 'Complete restoring site\'s docker-compose.yml' ); + } + + /** + * Check if new image is available for site's services. + * + * @param array $updated_images array of updated images. + * @param array $site_info array of site info. + * + * @return bool + */ + public static function is_site_service_image_changed( $updated_images, $site_info ) { + + chdir( $site_info['site_fs_path'] ); + $launch = EE::launch( 'docker-compose images' ); + $lines = explode( PHP_EOL, trim( $launch->stdout ) ); + $site_images = []; + + for ( $i = 1; $i < count( $lines ); $i ++ ) { + $columns = preg_split( '/\s+/', $lines[ $i ] ); + + if ( isset( $columns[1] ) ) { + $site_images[] = $columns[1]; + } + } + + $common_image = array_intersect( $updated_images, $site_images ); + + if ( ! empty( $common_image ) ) { + return true; + } + + return false; + } + + /** + * Generate docker-compose.yml for specific site. + * + * @param array $site_info array of site information. + * @param object $site_object Object of the particular site-type. + */ + public static function generate_site_docker_compose_file( $site_info, $site_object ) { + $site_object->populate_site_info( $site_info['site_url'] ); + EE::debug( "Start generating new docker-compose.yml for ${site_info['site_url']}" ); + $site_object->dump_docker_compose_yml( [ 'nohttps' => ! $site_info['site_ssl'] ] ); + EE::debug( "Complete generating new docker-compose.yml for ${site_info['site_url']}" ); + } + + /** + * Enable site. + * + * @param array $site_info array of site information. + * @param object $site_object object of site-type( HTML, PHP, WordPress ). + * + * @throws \Exception + */ + public static function enable_default_containers( $site_info, $site_object ) { + EE::debug( "Start enabling default containers of ${site_info['site_url']}" ); + + try { + $site_object->enable( [ $site_info['site_url'] ], [ 'force' => true ], false ); + } catch ( \Exception $e ) { + throw new \Exception( $e->getMessage() ); + } + + EE::debug( "Complete enabling default containers of ${site_info['site_url']}" ); + } + + /** + * Disable site. + * + * @param array $site_info array of site information. + * + * @throws \Exception + */ + public static function disable_default_containers( $site_info ) { + EE::debug( "Start disabling default containers of ${site_info['site_url']}" ); + + if ( ! chdir( $site_info['site_fs_path'] ) ) { + throw new \Exception( sprintf( '%s path does not exist', $site_info['site_fs_path'] ) ); + } + + if ( ! EE::exec( 'docker-compose stop && docker-compose rm -f' ) ) { + throw new \Exception( sprintf( 'Something went wrong on disable site %s', $site_info['site_url'] ) ); + } + + EE::debug( "Complete disabling default containers of ${site_info['site_url']}" ); + } + + /** + * Function to delete given volume. + * + * @param string $volume_name Name of the volume to be deleted. + * @param string $symlink_path Corresponding symlink to be removed. + */ + public static function delete_volume( $volume_name, $symlink_path ) { + $fs = new Filesystem(); + \EE::exec( 'docker volume rm ' . $volume_name ); + $fs->remove( $symlink_path ); + } + + /** + * Function to create given volume. + * + * @param string|array $site Name of the site or array of site having site_url. + * @param string $volume_name Name of the volume to be created. + * @param string $symlink_path Corresponding symlink to be created. + */ + public static function create_volume( $site, $volume_name, $symlink_path ) { + $site_url = is_array( $site ) ? $site['site_url'] : $site; + $volumes = [ + [ + 'name' => $volume_name, + 'path_to_symlink' => $symlink_path, + ], + ]; + \EE_DOCKER::create_volumes( $site_url, $volumes ); + } + + /** + * Function to backup and restore file/directory. + * + * @param string $destination Destination path. + * @param string $source Source path. + * @param bool $delete_different Delete files in $destination that are not there in source. + */ + public static function backup_restore( $source, $destination = '', $delete_different = true ) { + $fs = new Filesystem(); + $destination = empty( $destination ) ? EE_BACKUP_DIR . '/' . basename( $source ) : $destination; + EE::debug( "Copying files from: $source to $destination" ); + if ( is_file( $source ) ) { + $fs->copy( $source, $destination, true ); + } else { + $copy_options = [ + 'override' => true, + 'delete' => $delete_different, + ]; + $fs->mirror( $source, $destination, null, $copy_options ); + } + } + + /** + * Function to delete file/directory. + * + * @param string|array $path_to_delete File(s)/Director(y/ies) to be deleted. + */ + public static function delete( $path_to_delete ) { + $fs = new Filesystem(); + $fs->remove( $path_to_delete ); + } + + /** + * Function to reload site's nginx. + * + * @param string $site_fs_path Directory containing site's docker-compose.yml. + */ + public static function reload_nginx( $site_fs_path ) { + + chdir( $site_fs_path ); + $success = EE::exec( "docker-compose exec nginx sh -c 'nginx -t && nginx -s reload'" ); + if ( ! $success ) { + throw new \Exception( 'Could not reload nginx. Check logs.' ); + } + } + + /** + * Function to reload site's php. + * + * @param string $site_fs_path Directory containing site's docker-compose.yml. + */ + public static function reload_php( $site_fs_path ) { + + chdir( $site_fs_path ); + $success = EE::exec( "docker-compose exec php bash -c 'kill -USR2 1'" ); + if ( ! $success ) { + throw new \Exception( 'Could not reload php. Check logs.' ); + } + } + + /** + * Function to pull site docker-compose images. + * + * @param string $site_fs_path Directory containing site's docker-compose.yml. + */ + public static function docker_compose_pull( $site_fs_path ) { + + chdir( $site_fs_path ); + $success = EE::exec( "docker-compose pull" ); + if ( ! $success ) { + throw new \Exception( 'Could pull given images.' ); + } + } + + /** + * Enable support containers for sites. + * + * @param $site_url string Site URL. + * @param $site_fs_path string File system path of site. + * + * @throws \Exception + */ + public static function enable_support_containers( $site_url, $site_fs_path ) { + EE::debug( sprintf( 'Start enabling containers for %s', $site_url ) ); + + $site_name = str_replace( '.', '', $site_url ); + $project_name = sprintf( 'update-ee-%s', $site_name ); + + if ( ! chdir( $site_fs_path ) ) { + throw new \Exception( sprintf( '%s does not exist.', $site_fs_path ) ); + } + + $command = sprintf( 'docker-compose --project-name=%s up -d nginx', $project_name ); + if ( ! EE::exec( $command ) ) { + throw new \Exception( sprintf( 'Unable to create support container for %s', $site_url ) ); + } + + EE::debug( sprintf( 'Complete enabling support containers for %s', $site_url ) ); + } + + /** + * Disable support containers for sites. + * + * @param $site_url string Site URL. + * @param $site_fs_path string File system path of site. + * + * @throws \Exception + */ + public static function disable_support_containers( $site_url, $site_fs_path ) { + EE::debug( sprintf( 'Start disabling support containers for %s', $site_url ) ); + + $site_name = str_replace( '.', '', $site_url ); + $project_name = sprintf( 'update-ee-%s', $site_name ); + + if ( ! chdir( $site_fs_path ) ) { + throw new \Exception( sprintf( '%s does not exist.', $site_fs_path ) ); + } + + $command = sprintf( 'docker-compose --project-name=%1$s stop && docker-compose --project-name=%1$s rm -f', $project_name ); + if ( ! EE::exec( $command ) ) { + throw new \Exception( sprintf( 'Unable to remove support container for %s', $site_url ) ); + } + + EE::debug( sprintf( 'Complete disabling support containers for %s', $site_url ) ); + } +} diff --git a/php/EE/Model/Base.php b/php/EE/Model/Base.php new file mode 100644 index 000000000..cb187beed --- /dev/null +++ b/php/EE/Model/Base.php @@ -0,0 +1,311 @@ +fields = $fields; + } + + /** + * Throws exception if model is not found + * + * @param string $value value to find + * @param string $column Column to find in. Defaults to primary key + * + * @throws \Exception + * + * @return static + */ + public static function find_or_fail( string $value, string $column = null ) { + $model = static::find( $value, $column ); + + if ( ! $model ) { + throw new \Exception( sprintf( 'Unable to find %s : with primary key: %s and value: %s', __CLASS__, static::$primary_key, $value ) ); + } + + return $model; + } + + /** + * Returns single model fetched by primary key + * + * @param string $value value to find + * @param array $columns Columns to select. + * + * @throws \Exception + * + * @return bool|static + */ + public static function find( string $value, array $columns = [] ) { + + $model = EE::db() + ->table( static::$table ) + ->select( ...$columns ) + ->where( static::$primary_key, $value ) + ->first(); + + if ( false === $model ) { + return false; + } + + return static::single_array_to_model( $model ); + } + + /** + * Converts single row fetched from database into model + * + * @param array $arr Associative array representing a row from database + * + * @return mixed + */ + protected static function single_array_to_model( array $arr ) { + return new static( $arr ); + } + + /** + * Exits with error if model is not found + * + * @param string $value value to find + * @param string $column Column to find in. Defaults to primary key + * + * @throws \Exception + * + * @return static + */ + public static function find_or_error( string $value, string $column = null ) { + $model = static::find( $value, $column ); + + if ( ! $model ) { + EE::error( sprintf( 'Unable to find %s : with primary key: %s and value: %s', __CLASS__, static::$primary_key, $value ) ); + } + + return $model; + } + + /** + * Returns all models. + * + * @param array $columns Columns to select + * + * @throws \Exception + * + * @return array Array of models + */ + public static function all( $columns = [] ) { + $models = EE::db() + ->table( static::$table ) + ->select( ...$columns ) + ->get(); + + return static::many_array_to_model( $models ); + } + + /** + * Converts an array of result from database to models + * + * @param array $arr Array of results from database + * + * @return array + */ + protected static function many_array_to_model( array $arr ) { + $result = []; + + foreach ( $arr as $model ) { + $result[] = static::single_array_to_model( $model ); + } + + return $result; + } + + /** + * Creates new entity in DB + * + * @param array $columns Columns and values to insert + * + * @throws \Exception + * + * @return bool Model created successfully + */ + public static function create( $columns = [] ) { + return EE::db()->table( static::$table )->insert( $columns ); + } + + /** + * Returns all model with condition + * + * @param string|array $column Column to search in + * @param string $value Value to match + * + * @throws \Exception + * + * @return array + */ + public static function where( $column, $value = '' ) { + + if ( is_bool( $value ) ) { + $value = (int) $value; + } + + return static::many_array_to_model( + EE::db() + ->table( static::$table ) + ->where( $column, $value ) + ->all() + ); + } + + /** + * In model, every value is get in fields array. + * We populate it either during constructor or during find() method call + * + * This gives us benefit that models do not have to define properties in class + * They are automatically defined when fetched from database! + * + * @param string $name Name of property to get + * + * @throws \Exception + * + * @return mixed Value of property + */ + public function __get( string $name ) { + if ( array_key_exists( $name, $this->fields ) ) { + return $this->fields[ $name ]; + } + + throw new \Exception( "Unable to find variable: $name" ); + } + + /** + * In model, every value is set in fields array. + * + * This gives us benefit that models do not have to define the logic of saving them in database. + * While saving models, we use the $fields array to save it in database + * + * + * @param string $name Name of property to set + * @param mixed $value Value of property to set + */ + public function __set( string $name, $value ) { + $this->fields[$name] = $value; + } + + /** + * Overriding isset for correct behaviour while using isset on model objects + * + * @param string|int $name Name of property to check + * + * @return bool + */ + public function __isset( $name ) { + return isset( $this->fields[$name] ); + } + + /** + * Removes a property from model + * It's done by removing it from $fields array + * + * @param string $name Name of property to unset + */ + public function __unset( $name ) { + unset( $this->fields[$name] ); + } + + /** + * Saves current model into database + * + * @throws \Exception + * + * @return bool Model saved successfully + */ + public function save() { + + $fields = $this->fields; + + foreach ( $fields as $key => $value ) { + if ( is_bool( $value ) ) { + $fields[ $key ] = (int) $value; + } + } + + if ( static::$needs_update_timestamp ) { + $fields = array_merge( + $fields, [ + 'modified_on' => date( 'Y-m-d H:i:s' ), + ] + ); + } + + $primary_key_column = static::$primary_key; + + return EE::db() + ->table( static::$table ) + ->where( $primary_key_column, $this->$primary_key_column ) + ->update( $fields ); + } + + /** + * Deletes current model from database + * + * @throws \Exception + * + * @return bool Model deleted successfully + */ + public function delete() { + $primary_key_column = static::$primary_key; + + return EE::db() + ->table( static::$table ) + ->where( $primary_key_column, $this->$primary_key_column ) + ->delete(); + } + + /** + * Updates current model from database + * + * @param array $where Where conditions to update + * @param array $updated_values Updated values + * + * @throws \Exception + * + * @return bool Model updated successfully + */ + public static function update( array $where, array $updated_values ) { + + return EE::db() + ->table( static::$table ) + ->where( $where ) + ->update( $updated_values ); + } +} diff --git a/php/EE/Model/Migration.php b/php/EE/Model/Migration.php new file mode 100644 index 000000000..5991d7a07 --- /dev/null +++ b/php/EE/Model/Migration.php @@ -0,0 +1,25 @@ +table( 'migrations' ) + ->select( 'migration' ) + ->get(); + + return array_column( $migrations, 'migration' ); + } + +} diff --git a/php/EE/Model/Option.php b/php/EE/Model/Option.php new file mode 100644 index 000000000..3900193d2 --- /dev/null +++ b/php/EE/Model/Option.php @@ -0,0 +1,53 @@ + $key, + 'value' => $value, + ] + ); + } + + $existing_key->value = $value; + + return $existing_key->save(); + } + + /** + * Gets a key from options table + * + * @param string $key Key of option + * + * @throws \Exception + * @return bool|Option + */ + public static function get( string $key ) { + $option = static::find( $key ); + + return false === $option ? false : $option->value; + } +} diff --git a/php/EE/NoOp.php b/php/EE/NoOp.php new file mode 100644 index 000000000..45dda29e6 --- /dev/null +++ b/php/EE/NoOp.php @@ -0,0 +1,18 @@ + 'pre_install', + PackageEvents::POST_PACKAGE_INSTALL => 'post_install', + ); + } + + public static function pre_install( PackageEvent $event ) { + $operation_message = $event->getOperation()->__toString(); + EE::log( ' - ' . $operation_message ); + } + + public static function post_install( PackageEvent $event ) { + + $operation = $event->getOperation(); + $reason = $operation->getReason(); + if ( $reason instanceof Rule ) { + + switch ( $reason->getReason() ) { + + case Rule::RULE_PACKAGE_CONFLICT: + case Rule::RULE_PACKAGE_SAME_NAME: + case Rule::RULE_PACKAGE_REQUIRES: + $composer_error = $reason->getPrettyString( $event->getPool() ); + break; + + } + + if ( ! empty( $composer_error ) ) { + EE::log( sprintf( ' - Warning: %s', $composer_error ) ); + } + } + + } + +} diff --git a/php/EE/Process.php b/php/EE/Process.php new file mode 100644 index 000000000..351d51e39 --- /dev/null +++ b/php/EE/Process.php @@ -0,0 +1,136 @@ + STDIN, + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ); + + /** + * @var bool Whether to log run time info or not. + */ + public static $log_run_times = false; + + /** + * @var array Array of process run time info, keyed by process command, each a 2-element array containing run time and run count. + */ + public static $run_times = array(); + + /** + * @param string $command Command to execute. + * @param string $cwd Directory to execute the command in. + * @param array $env Environment variables to set when running the command. + * + * @return Process + */ + public static function create( $command, $cwd = null, $env = array() ) { + $proc = new self; + + $proc->command = $command; + $proc->cwd = $cwd; + $proc->env = $env; + + return $proc; + } + + private function __construct() {} + + /** + * Run the command. + * + * @return ProcessRun + */ + public function run() { + $start_time = microtime( true ); + + $proc = Utils\proc_open_compat( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env ); + + $stdout = stream_get_contents( $pipes[1] ); + fclose( $pipes[1] ); + + $stderr = stream_get_contents( $pipes[2] ); + fclose( $pipes[2] ); + + $return_code = proc_close( $proc ); + + $run_time = microtime( true ) - $start_time; + + if ( self::$log_run_times ) { + if ( ! isset( self::$run_times[ $this->command ] ) ) { + self::$run_times[ $this->command ] = array( 0, 0 ); + } + self::$run_times[ $this->command ][0] += $run_time; + self::$run_times[ $this->command ][1]++; + } + + return new ProcessRun( + array( + 'stdout' => $stdout, + 'stderr' => $stderr, + 'return_code' => $return_code, + 'command' => $this->command, + 'cwd' => $this->cwd, + 'env' => $this->env, + 'run_time' => $run_time, + ) + ); + } + + /** + * Run the command, but throw an Exception on error. + * + * @return ProcessRun + */ + public function run_check() { + $r = $this->run(); + + // $r->STDERR is incorrect, but kept incorrect for backwards-compat + if ( $r->return_code || ! empty( $r->STDERR ) ) { + throw new \RuntimeException( $r ); + } + + return $r; + } + + /** + * Run the command, but throw an Exception on error. + * Same as `run_check()` above, but checks the correct stderr. + * + * @return ProcessRun + */ + public function run_check_stderr() { + $r = $this->run(); + + if ( $r->return_code || ! empty( $r->stderr ) ) { + throw new \RuntimeException( $r ); + } + + return $r; + } +} diff --git a/php/EE/ProcessRun.php b/php/EE/ProcessRun.php new file mode 100644 index 000000000..3c225a981 --- /dev/null +++ b/php/EE/ProcessRun.php @@ -0,0 +1,68 @@ + $value ) { + $this->$key = $value; + } + } + + /** + * Return properties of executed command as a string. + * + * @return string + */ + public function __toString() { + $out = "$ $this->command\n"; + $out .= "$this->stdout\n$this->stderr"; + $out .= "cwd: $this->cwd\n"; + $out .= "run time: $this->run_time\n"; + $out .= "exit status: $this->return_code"; + + return $out; + } + +} diff --git a/php/EE/RevertableStepProcessor.php b/php/EE/RevertableStepProcessor.php new file mode 100644 index 000000000..fde925787 --- /dev/null +++ b/php/EE/RevertableStepProcessor.php @@ -0,0 +1,100 @@ +steps[] = [ + 'up' => $up_step, + 'up_params' => $up_params, + 'down' => $down_step, + 'down_params' => $down_params, + 'context' => $context, + ]; + + return $this; // Returns this to enable method chaining + } + + /** + * Executes all pending steps. Reverts the steps if any one step throws error. + * + * @return boolean Returns if the pending steps were executed successfully. + */ + public function execute( $show_error = true ) { + $steps_to_execute = array_slice( $this->steps, $this->execution_index ); + + foreach ( $steps_to_execute as $step ) { + $context = $step['context']; + try { + EE::debug( "Executing $context... " ); + call_user_func_array( $step['up'], $step['up_params'] ?? [] ); + $this->execution_index ++; + EE::debug( "Executed $context." ); + } catch ( \Exception $e ) { + $exception_message = $e->getMessage(); + $callable = EE\Utils\get_callable_name( $step['up'] ); + if ( $show_error ) { + EE::error( "Error while executing $context. $callable: $exception_message", false ); + } + $this->rollback(); + $this->steps = []; + + return false; + } + } + + return true; + } + + /** + * Rolls back all executed steps. + */ + public function rollback() { + while ( $this->execution_index >= 0 ) { + if ( ! array_key_exists( $this->execution_index, $this->steps ) ) { + $this->execution_index--; + continue; + } + $step = $this->steps[ $this->execution_index ]; + $context = $step['context']; + try { + EE::debug( "Reverting $context... " ); + if ( null !== $step['down'] ) { + call_user_func_array( $step['down'], $step['down_params'] ?? [] ); + } + EE::debug( "Reverted $context" ); + } catch ( \Exception $e ) { + $exception_message = $e->getMessage(); + EE::debug( "Encountered error while reverting $context: $exception_message. If possible, do it manually" ); + } finally { + $this->execution_index --; + } + } + } +} diff --git a/php/EE/Runner.php b/php/EE/Runner.php new file mode 100644 index 000000000..1f22ac9cf --- /dev/null +++ b/php/EE/Runner.php @@ -0,0 +1,994 @@ +$key; + } + + /** + * Function to check and create the root directory for ee. + */ + private function init_ee() { + + $this->ensure_present_in_config( 'locale', 'en_US' ); + $this->ensure_present_in_config( 'ee_installer_version', 'stable' ); + + define( 'DB', EE_ROOT_DIR . '/db/ee.sqlite' ); + define( 'LOCALHOST_IP', '127.0.0.1' ); + define( 'SITE_CUSTOM_DOCKER_COMPOSE', 'docker-compose-custom.yml' ); + define( 'SITE_CUSTOM_DOCKER_COMPOSE_DIR', 'user-docker-compose' ); + + $db_dir = dirname( DB ); + if ( ! is_dir( $db_dir ) ) { + mkdir( $db_dir ); + } + + $check_requirements = false; + if ( ! empty( $this->arguments ) ) { + $check_requirements = in_array( $this->arguments[0], [ 'cli', 'config', 'help' ], true ) ? false : true; + $check_requirements = ( [ 'site', 'cmd-dump' ] === $this->arguments ) ? false : $check_requirements; + } + + define( 'EE_PROXY_TYPE', 'services_global-nginx-proxy_1' ); + if ( $check_requirements ) { + $this->check_requirements(); + $this->maybe_trigger_migration(); + } + + if ( [ 'cli', 'info' ] === $this->arguments && $this->check_requirements( false ) ) { + $this->maybe_trigger_migration(); + } + } + + /** + * Check EE requirements for required commands. + * + * @param bool $show_error To display error or to retutn status. + */ + public function check_requirements( $show_error = true ) { + + $docker_running = true; + $status = true; + $error = []; + + $docker_running_cmd = 'docker ps > /dev/null'; + if ( ! EE::exec( $docker_running_cmd ) ) { + $status = false; + $docker_running = false; + $error[] = 'Docker not installed or not running.'; + } + + $docker_compose_installed = 'command -v docker-compose > /dev/null'; + if ( ! EE::exec( $docker_compose_installed ) ) { + $status = false; + $error[] = 'EasyEngine requires docker-compose.'; + } + + $docker_compose_version = trim( EE::launch( 'docker-compose version --short' )->stdout ); + if ( version_compare( $docker_compose_version, '2.26.1', '<' ) ) { + if ( $show_error ) { + EE::warning( 'EasyEngine requires docker-compose version 2.26.1 or greater.' ); + EE::warning( 'You can get the updated version of docker-compose from assets in https://github.com/docker/compose/releases/tag/v2.26.1' ); + } + } + + if ( version_compare( PHP_VERSION, '7.2.0' ) < 0 ) { + $status = false; + $error[] = 'EasyEngine requires minimum PHP 7.2.0 to run.'; + } + + if ( $show_error && ! $status ) { + EE::error( reset( $error ), false ); + if ( IS_DARWIN && ! $docker_running ) { + EE::log( 'For macOS docker can be installed using: `brew cask install docker`' ); + } + die; + } + + return $status; + } + + /** + * Function to run migrations required to upgrade to the newer version. Will always be invoked from the newer phar downloaded inside the /tmp folder + */ + private function migrate() { + + // Check if minimum 5GB disk space is available + $required_space = 5 * 1024 * 1024 * 1024; + $free_space = 0; + $free_space_docker = 0; + + if ( is_dir( EE_ROOT_DIR ) ) { + $free_space = disk_free_space( EE_ROOT_DIR ); + } + + $docker_dir = trim( EE::launch( 'docker info --format \'{{.DockerRootDir}}\'' )->stdout ); + + if ( is_dir( $docker_dir ) ) { + $free_space_docker = disk_free_space( $docker_dir ); + } + + if ( ( $free_space < $required_space && is_dir( EE_ROOT_DIR ) ) || ( $free_space_docker < $required_space && is_dir( $docker_dir ) ) ) { + EE::error( 'EasyEngine update requires minimum 5GB disk space to run. Please free up some space and try again.' ); + } + + $rsp = new \EE\RevertableStepProcessor(); + + $rsp->add_step( 'ee-db-migrations', 'EE\Migration\Executor::execute_migrations' ); + $rsp->add_step( 'ee-custom-container-migrations', 'EE\Migration\CustomContainerMigrations::execute_migrations' ); + $rsp->add_step( 'ee-docker-image-migrations', 'EE\Migration\Containers::start_container_migration' ); + $rsp->add_step( 'ee-update-docker-compose', 'EE\Migration\Containers::update_docker_compose' ); + $rsp->add_step( 'ee-update-cron-config', 'EE\Cron\Utils\update_cron_config' ); + + return $rsp->execute(); + } + + /** + * Register a command for early invocation, generally before WordPress loads. + * + * @param string $when Named execution hook + * @param EE\Dispatcher\Subcommand $command + */ + public function register_early_invoke( $when, $command ) { + $this->_early_invoke[ $when ][] = array_slice( Dispatcher\get_path( $command ), 1 ); + } + + /** + * Perform the early invocation of a command. + * + * @param string $when Named execution hook + */ + private function do_early_invoke( $when ) { + if ( ! isset( $this->_early_invoke[ $when ] ) ) { + return; + } + + // Search the value of @when from the command method. + $real_when = ''; + $r = $this->find_command_to_run( $this->arguments ); + if ( is_array( $r ) ) { + list( $command, $final_args, $cmd_path ) = $r; + + foreach ( $this->_early_invoke as $_when => $_path ) { + foreach ( $_path as $cmd ) { + if ( $cmd === $cmd_path ) { + $real_when = $_when; + } + } + } + } + + foreach ( $this->_early_invoke[ $when ] as $path ) { + if ( $this->cmd_starts_with( $path ) ) { + if ( empty( $real_when ) || ( $real_when && $real_when === $when ) ) { + $this->_run_command_and_exit(); + } + } + } + } + + /** + * Get the path to the global configuration YAML file. + * + * @return string|false + */ + public function get_global_config_path() { + + if ( getenv( 'EE_CONFIG_PATH' ) ) { + $config_path = getenv( 'EE_CONFIG_PATH' ); + $this->_global_config_path_debug = 'Using global config from EE_CONFIG_PATH env var: ' . $config_path; + } else { + $config_path = EE_ROOT_DIR . '/config/config.yml'; + $this->_global_config_path_debug = 'Using default global config: ' . $config_path; + } + + if ( is_readable( $config_path ) ) { + return $config_path; + } + + $this->_global_config_path_debug = 'No readable global config found'; + + return false; + } + + /** + * Get the path to the project-specific configuration + * YAML file. + * ee.local.yml takes priority over ee.yml. + * + * @return string|false + */ + public function get_project_config_path() { + $config_files = array( + 'ee.local.yml', + 'ee.yml', + ); + + // Stop looking upward when we find we have emerged from a subdirectory + // install into a parent install + $project_config_path = Utils\find_file_upward( + $config_files, + getcwd(), + function ( $dir ) { + return false; + } + ); + + $this->_project_config_path_debug = 'No project config found'; + + if ( ! empty( $project_config_path ) ) { + $this->_project_config_path_debug = 'Using project config: ' . $project_config_path; + } + + return $project_config_path; + } + + /** + * Get the path to the packages directory + * + * @return string + */ + public function get_packages_dir_path() { + if ( getenv( 'EE_PACKAGES_DIR' ) ) { + $packages_dir = Utils\trailingslashit( getenv( 'EE_PACKAGES_DIR' ) ); + } else { + $packages_dir = EE_ROOT_DIR . '/packages'; + } + return $packages_dir; + } + + private function cmd_starts_with( $prefix ) { + return array_slice( $this->arguments, 0, count( $prefix ) ) === $prefix; + } + + /** + * Given positional arguments, find the command to execute. + * + * @param array $args + * @return array|string Command, args, and path on success; error message on failure + */ + public function find_command_to_run( $args ) { + $command = \EE::get_root_command(); + + EE::do_hook( 'find_command_to_run_pre' ); + + $cmd_path = array(); + + while ( ! empty( $args ) && $command->can_have_subcommands() ) { + $cmd_path[] = $args[0]; + $full_name = implode( ' ', $cmd_path ); + + $subcommand = $command->find_subcommand( $args ); + + if ( ! $subcommand ) { + if ( count( $cmd_path ) > 1 ) { + $child = array_pop( $cmd_path ); + $parent_name = implode( ' ', $cmd_path ); + $suggestion = $this->get_subcommand_suggestion( $child, $command ); + return sprintf( + "'%s' is not a registered subcommand of '%s'. See 'ee help %s' for available subcommands.%s", + $child, + $parent_name, + $parent_name, + ! empty( $suggestion ) ? PHP_EOL . "Did you mean '{$suggestion}'?" : '' + ); + } + + $suggestion = $this->get_subcommand_suggestion( $full_name, $command ); + + return sprintf( + "'%s' is not a registered ee command. See 'ee help' for available commands.%s", + $full_name, + ! empty( $suggestion ) ? PHP_EOL . "Did you mean '{$suggestion}'?" : '' + ); + } + + if ( $this->is_command_disabled( $subcommand ) ) { + return sprintf( + "The '%s' command has been disabled from the config file.", + $full_name + ); + } + + $command = $subcommand; + } + + return array( $command, $args, $cmd_path ); + } + + /** + * Find the EE command to run given arguments, and invoke it. + * + * @param array $args Positional arguments including command name + * @param array $assoc_args Associative arguments for the command. + * @param array $options Configuration options for the function. + */ + public function run_command( $args, $assoc_args = array(), $options = array() ) { + + $r = $this->find_command_to_run( $args ); + if ( is_string( $r ) ) { + EE::error( $r ); + } + + list( $command, $final_args, $cmd_path ) = $r; + + $name = implode( ' ', $cmd_path ); + + $extra_args = array(); + + if ( isset( $this->extra_config[ $name ] ) ) { + $extra_args = $this->extra_config[ $name ]; + } + + EE::debug( 'Running command: ' . $name, 'bootstrap' ); + try { + $command->invoke( $final_args, $assoc_args, $extra_args ); + } catch ( EE\Iterators\Exception $e ) { + EE::error( $e->getMessage() ); + } + } + + /** + * Show synopsis if the called command is a composite command + */ + public function show_synopsis_if_composite_command() { + $r = $this->find_command_to_run( $this->arguments ); + if ( is_array( $r ) ) { + list( $command ) = $r; + + if ( $command->can_have_subcommands() ) { + $command->show_usage(); + exit; + } + } + } + + private function _run_command_and_exit( $help_exit_warning = '' ) { + $this->show_synopsis_if_composite_command(); + $this->run_command( $this->arguments, $this->assoc_args ); + if ( $this->cmd_starts_with( array( 'help' ) ) ) { + // Help couldn't find the command so exit with suggestion. + $suggestion_or_disabled = $this->find_command_to_run( array_slice( $this->arguments, 1 ) ); + if ( is_string( $suggestion_or_disabled ) ) { + if ( $help_exit_warning ) { + EE::warning( $help_exit_warning ); + } + EE::error( $suggestion_or_disabled ); + } + // Should never get here. + } + exit; + } + + /** + * Perform a command against a remote server over SSH (or a container using + * scheme of "docker" or "docker-compose"). + * + * @param string $connection_string Passed connection string. + * @return void + */ + private function run_ssh_command( $connection_string ) { + + EE::do_hook( 'before_ssh' ); + + $bits = Utils\parse_ssh_url( $connection_string ); + + $pre_cmd = getenv( 'EE_SSH_PRE_CMD' ); + if ( $pre_cmd ) { + $pre_cmd = rtrim( $pre_cmd, ';' ) . '; '; + } + if ( ! empty( $bits['path'] ) ) { + $pre_cmd .= 'cd ' . escapeshellarg( $bits['path'] ) . '; '; + } + + $env_vars = ''; + if ( getenv( 'EE_STRICT_ARGS_MODE' ) ) { + $env_vars .= 'EE_STRICT_ARGS_MODE=1 '; + } + + $ee_binary = 'ee'; + $ee_args = array_slice( $GLOBALS['argv'], 1 ); + + if ( $this->alias && ! empty( $ee_args[0] ) && $this->alias === $ee_args[0] ) { + array_shift( $ee_args ); + $runtime_alias = array(); + foreach ( $this->aliases[ $this->alias ] as $key => $value ) { + if ( 'ssh' === $key ) { + continue; + } + $runtime_alias[ $key ] = $value; + } + if ( ! empty( $runtime_alias ) ) { + $encoded_alias = json_encode( + array( + $this->alias => $runtime_alias, + ) + ); + $ee_binary = "EE_RUNTIME_ALIAS='{$encoded_alias}' {$ee_binary} {$this->alias}"; + } + } + + foreach ( $ee_args as $k => $v ) { + if ( preg_match( '#--ssh=#', $v ) ) { + unset( $ee_args[ $k ] ); + } + } + + $ee_command = $pre_cmd . $env_vars . $ee_binary . ' ' . implode( ' ', array_map( 'escapeshellarg', $ee_args ) ); + $escaped_command = $this->generate_ssh_command( $bits, $ee_command ); + + passthru( $escaped_command, $exit_code ); + if ( 255 === $exit_code ) { + EE::error( 'Cannot connect over SSH using provided configuration.', 255 ); + } else { + exit( $exit_code ); + } + } + + /** + * Generate a shell command from the parsed connection string. + * + * @param array $bits Parsed connection string. + * @param string $ee_command EE command to run. + * @return string + */ + private function generate_ssh_command( $bits, $ee_command ) { + $escaped_command = ''; + + // Set default values. + foreach ( array( 'scheme', 'user', 'host', 'port', 'path' ) as $bit ) { + if ( ! isset( $bits[ $bit ] ) ) { + $bits[ $bit ] = null; + } + + EE::debug( 'SSH ' . $bit . ': ' . $bits[ $bit ], 'bootstrap' ); + } + + $is_tty = function_exists( 'posix_isatty' ) && posix_isatty( STDOUT ); + + if ( 'docker' === $bits['scheme'] ) { + $command = 'docker exec %s%s%s sh -c %s'; + + $escaped_command = sprintf( + $command, + $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', + $is_tty ? '-t ' : '', + escapeshellarg( $bits['host'] ), + escapeshellarg( $ee_command ) + ); + } + + if ( 'docker-compose' === $bits['scheme'] ) { + $command = \EE_DOCKER::docker_compose_with_custom() . ' exec %s%s%s sh -c %s'; + + $escaped_command = sprintf( + $command, + $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', + $is_tty ? '' : '-T ', + escapeshellarg( $bits['host'] ), + escapeshellarg( $ee_command ) + ); + } + + // Vagrant ssh-config. + if ( 'vagrant' === $bits['scheme'] ) { + $command = 'vagrant ssh -c %s %s'; + + $escaped_command = sprintf( + $command, + escapeshellarg( $ee_command ), + escapeshellarg( $bits['host'] ) + ); + } + + // Default scheme is SSH. + if ( 'ssh' === $bits['scheme'] || null === $bits['scheme'] ) { + $command = 'ssh -q %s%s %s %s'; + + if ( $bits['user'] ) { + $bits['host'] = $bits['user'] . '@' . $bits['host']; + } + + $escaped_command = sprintf( + $command, + $bits['port'] ? '-p ' . (int) $bits['port'] . ' ' : '', + escapeshellarg( $bits['host'] ), + $is_tty ? '-t' : '-T', + escapeshellarg( $ee_command ) + ); + } + + EE::debug( 'Running SSH command: ' . $escaped_command, 'bootstrap' ); + + return $escaped_command; + } + + /** + * Check whether a given command is disabled by the config + * + * @return bool + */ + public function is_command_disabled( $command ) { + $path = implode( ' ', array_slice( \EE\Dispatcher\get_path( $command ), 1 ) ); + return in_array( $path, $this->config['disabled_commands'] ); + } + + /** + * Whether or not the output should be rendered in color + * + * @return bool + */ + public function in_color() { + return $this->colorize; + } + + public function init_colorization() { + if ( ! isset( $this->config['color'] ) || 'auto' === $this->config['color'] ) { + $this->colorize = ( ! \EE\Utils\isPiped() && ! \EE\Utils\is_windows() ); + } else { + $this->colorize = $this->config['color']; + } + } + + public function init_logger() { + if ( isset( $this->config['quiet'] ) && $this->config['quiet'] ) { + $logger = new \EE\Loggers\Quiet; + } else { + $logger = new \EE\Loggers\Regular( $this->in_color() ); + } + + EE::set_logger( $logger ); + + // Create the config directory if not exist for file logger to initialize. + if ( ! is_dir( EE_ROOT_DIR ) ) { + shell_exec('mkdir -p ' . EE_ROOT_DIR); + } + + if ( ! is_writable( EE_ROOT_DIR ) ) { + EE::err( 'Config root: ' . EE_ROOT_DIR . ' is not writable by EasyEngine' ); + } + + if ( isset( $GLOBALS['argv'][1] ) && 'cli' === $GLOBALS['argv'][1] && isset( $GLOBALS['argv'][2] ) && 'completions' === $GLOBALS['argv'][2] ) { + $file_logging_path = '/dev/null'; + } else { + $file_logging_path = EE_ROOT_DIR . '/logs/ee.log'; + } + + $dateFormat = 'd-m-Y H:i:s'; + $output = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + $formatter = new \Monolog\Formatter\LineFormatter( $output, $dateFormat, false, true ); + $stream = new \Monolog\Handler\StreamHandler( $file_logging_path, Logger::DEBUG ); + $stream->setFormatter( $formatter ); + $file_logger = new \Monolog\Logger( 'ee' ); + $file_logger->pushHandler( $stream ); + $file_logger->info( '::::::::::::::::::::::::ee invoked::::::::::::::::::::::::' ); + EE::set_file_logger( $file_logger ); + } + + public function get_required_files() { + return $this->_required_files; + } + + public function init_config() { + $configurator = \EE::get_configurator(); + + $argv = array_slice( $GLOBALS['argv'], 1 ); + + $this->alias = null; + if ( ! empty( $argv[0] ) && preg_match( '#' . Configurator::ALIAS_REGEX . '#', $argv[0], $matches ) ) { + $this->alias = array_shift( $argv ); + } + + // File config + { + $this->global_config_path = $this->get_global_config_path(); + $this->project_config_path = $this->get_project_config_path(); + + $configurator->merge_yml( $this->global_config_path, $this->alias ); + $config = $configurator->to_array(); + $configurator->merge_yml( $this->project_config_path, $this->alias ); + $config = $configurator->to_array(); + //$this->_required_files['project'] = $config[0]['require']; + } + + // Runtime config and args + { + list( $args, $assoc_args, $this->runtime_config ) = $configurator->parse_args( $argv ); + + // foo --help -> help foo + if ( isset( $assoc_args['help'] ) && ! in_array( 'wp', $args ) ) { + array_unshift( $args, 'help' ); + unset( $assoc_args['help'] ); + } + + if ( empty( $args ) && isset( $assoc_args['version'] ) ) { + array_unshift( $args, 'version' ); + array_unshift( $args, 'cli' ); + unset( $assoc_args['version'] ); + } + + list( $this->arguments, $this->assoc_args ) = [ $args, $assoc_args ]; + + $configurator->merge_array( $this->runtime_config ); + } + + list( $this->config, $this->extra_config ) = $configurator->to_array(); + $this->aliases = $configurator->get_aliases(); + if ( count( $this->aliases ) && ! isset( $this->aliases['@all'] ) ) { + $this->aliases = array_reverse( $this->aliases ); + $this->aliases['@all'] = 'Run command against every registered alias.'; + $this->aliases = array_reverse( $this->aliases ); + } + //$this->_required_files['runtime'] = $this->config['require']; + } + + /** + * Ensures that vars are present in config. If they aren't, attempts to + * create config file and add vars in it. + * + * @param $var Variable to check. + * @param $default Default value to use if $var is not set. + */ + public function ensure_present_in_config( $var, $default ) { + $config_file_path = getenv( 'EE_CONFIG_PATH' ) ? getenv( 'EE_CONFIG_PATH' ) : EE_ROOT_DIR . '/config/config.yml'; + $existing_config = Spyc::YAMLLoad( $config_file_path ); + if ( ! isset( $existing_config[$var] ) ) { + $this->config[$var] = $default; + $config_dir_path = dirname( $config_file_path ); + + if ( ! is_dir( $config_dir_path ) ) { + mkdir( $config_dir_path, 0777, true ); + } + + if ( file_exists( $config_file_path ) ) { + if ( is_readable( $config_file_path ) ) { + if ( is_writable( $config_file_path ) ) { + $existing_config = Spyc::YAMLLoad( $config_file_path ); + $this->add_var_to_config_file( $var, $config_file_path, $existing_config ); + + return; + } + EE::error( "The config file {$config_file_path} is not writable. Please set a config path which is writable in EE_CONFIG_PATH environment variable." ); + } + EE::error( "The config file {$config_file_path} is not readable. Please select a config path which is readable in EE_CONFIG_PATH environment variable." ); + } else { + if ( is_writable( $config_dir_path ) ) { + $this->add_var_to_config_file( $var, $config_file_path ); + + return; + } + EE::err( "Configuration directory: $config_dir_path is not writable by EasyEngine." ); + } + } + } + + private function add_var_to_config_file( $var, $config_file_path, $config = [] ) { + $config[$var] = $this->config[$var]; + + $config_file = fopen( $config_file_path, "w" ); + fwrite( $config_file, Spyc::YAMLDump( $config ) ); + fclose( $config_file ); + } + + private function run_alias_group( $aliases ) { + Utils\check_proc_available( 'group alias' ); + + $php_bin = escapeshellarg( Utils\get_php_binary() ); + + $script_path = $GLOBALS['argv'][0]; + + if ( getenv( 'EE_CONFIG_PATH' ) ) { + $config_path = getenv( 'EE_CONFIG_PATH' ); + } else { + $config_path = EE_ROOT_DIR . '/config/config.yml'; + } + $config_path = escapeshellarg( $config_path ); + + foreach ( $aliases as $alias ) { + EE::log( $alias ); + $args = implode( ' ', array_map( 'escapeshellarg', $this->arguments ) ); + $assoc_args = Utils\assoc_args_to_str( $this->assoc_args ); + $runtime_config = Utils\assoc_args_to_str( $this->runtime_config ); + $full_command = "EE_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$alias} {$args}{$assoc_args}{$runtime_config}"; + $proc = Utils\proc_open_compat( $full_command, array( STDIN, STDOUT, STDERR ), $pipes ); + proc_close( $proc ); + } + } + + private function set_alias( $alias ) { + $orig_config = $this->config; + $alias_config = $this->aliases[ $this->alias ]; + $this->config = array_merge( $orig_config, $alias_config ); + foreach ( $alias_config as $key => $_ ) { + if ( isset( $orig_config[ $key ] ) && ! is_null( $orig_config[ $key ] ) ) { + $this->assoc_args[ $key ] = $orig_config[ $key ]; + } + } + } + + public function start() { + + $this->init_ee(); + + // Enable PHP error reporting to stderr if testing. + if ( getenv( 'BEHAT_RUN' ) ) { + $this->enable_error_reporting(); + } + + EE::debug( $this->_global_config_path_debug, 'bootstrap' ); + EE::debug( $this->_project_config_path_debug, 'bootstrap' ); + EE::debug( 'argv: ' . implode( ' ', $GLOBALS['argv'] ), 'bootstrap' ); + + if ( $this->alias ) { + if ( '@all' === $this->alias && ! isset( $this->aliases['@all'] ) ) { + EE::error( "Cannot use '@all' when no aliases are registered." ); + } + + if ( '@all' === $this->alias && is_string( $this->aliases['@all'] ) ) { + $aliases = array_keys( $this->aliases ); + $k = array_search( '@all', $aliases ); + unset( $aliases[ $k ] ); + $this->run_alias_group( $aliases ); + exit; + } + + if ( ! array_key_exists( $this->alias, $this->aliases ) ) { + $error_msg = "Alias '{$this->alias}' not found."; + $suggestion = Utils\get_suggestion( $this->alias, array_keys( $this->aliases ), $threshold = 2 ); + if ( $suggestion ) { + $error_msg .= PHP_EOL . "Did you mean '{$suggestion}'?"; + } + EE::error( $error_msg ); + } + // Numerically indexed means a group of aliases + if ( isset( $this->aliases[ $this->alias ][0] ) ) { + $group_aliases = $this->aliases[ $this->alias ]; + $all_aliases = array_keys( $this->aliases ); + if ( $diff = array_diff( $group_aliases, $all_aliases ) ) { + EE::error( "Group '{$this->alias}' contains one or more invalid aliases: " . implode( ', ', $diff ) ); + } + $this->run_alias_group( $group_aliases ); + exit; + } + + $this->set_alias( $this->alias ); + } + + if ( empty( $this->arguments ) ) { + $this->arguments[] = 'help'; + } + + // Protect 'cli info' from most of the runtime, + // except when the command will be run over SSH + if ( ! empty( $this->arguments[0] ) && 'cli' === $this->arguments[0] && ! empty( $this->arguments[1] ) && 'info' === $this->arguments[1] ) { + $this->_run_command_and_exit(); + } + + // First try at showing man page. + if ( $this->cmd_starts_with( array( 'help' ) ) ) { + $this->auto_check_update(); + $this->run_command( $this->arguments, $this->assoc_args ); + // Help didn't exit so failed to find the command at this stage. + } + + $this->_run_command_and_exit(); + + } + + /** + * Check whether there's a EE update available, and suggest update if so. + */ + private function auto_check_update() { + + // `ee cli update` only works with Phars at this time. + if ( ! Utils\inside_phar() ) { + return; + } + + $existing_phar = realpath( $_SERVER['argv'][0] ); + // Phar needs to be writable to be easily updateable. + if ( ! is_writable( $existing_phar ) || ! is_writable( dirname( $existing_phar ) ) ) { + return; + } + + // Only check for update when a human is operating. + if ( ! function_exists( 'posix_isatty' ) || ! posix_isatty( STDOUT ) ) { + return; + } + + // Allow hosts and other providers to disable automatic check update. + if ( getenv( 'EE_DISABLE_AUTO_CHECK_UPDATE' ) ) { + return; + } + + // Permit configuration of number of days between checks. + $days_between_checks = getenv( 'EE_AUTO_CHECK_UPDATE_DAYS' ); + if ( false === $days_between_checks ) { + $days_between_checks = 1; + } + + $cache = EE::get_cache(); + $cache_key = 'ee-update-check'; + // Bail early on the first check, so we don't always check on an unwritable cache. + if ( ! $cache->has( $cache_key ) ) { + $cache->write( $cache_key, time() ); + return; + } + + // Bail if last check is still within our update check time period. + $last_check = (int) $cache->read( $cache_key ); + if ( time() - ( 24 * 60 * 60 * $days_between_checks ) < $last_check ) { + return; + } + + // In case the operation fails, ensure the timestamp has been updated. + $cache->write( $cache_key, time() ); + + // Check whether any updates are available. + ob_start(); + EE::run_command( + array( 'cli', 'check-update' ), + array( + 'format' => 'count', + ) + ); + $count = ob_get_clean(); + if ( ! $count ) { + return; + } + + // Looks like an update is available, so let's prompt to update. + EE::run_command( array( 'cli', 'update' ) ); + // If the Phar was replaced, we can't proceed with the original process. + exit; + } + + /** + * Triggers migration if current phar version > version in ee_option table. + * Also, trigger migrations if phar version >= version in ee_option table but nightly version differ. + */ + private function maybe_trigger_migration() { + + $db_version = Option::get( 'version' ); + $current_version = EE_VERSION; + + if ( ! $db_version ) { + $this->trigger_migration( $current_version ); + EE::log( 'ee successfully setup.' ); + if ( IS_DARWIN ) { + EE::log( 'To create more than 27 sites follow these steps manually: ' . "\n" . + '1. Please open Docker Desktop and in taskbar, go to Preferences > Daemon > Advanced. ' . "\n" . + '2. If the file is empty, add the following:' . "\n" . + ' {' . "\n" . + '"default-address-pools": [{"base":"10.0.0.0/8","size":24}]' . "\n" . + ' }' . "\n" . + 'If the file already contains JSON, just add the key "default-address-pools": [{"base":"10.0.0.0/8","size":24}] being careful to add a comma to the end of the line if it is not the last line before the closing bracket.' . "\n" . + '3. Restart Docker' ); + } + return; + } + + $base_db_version = preg_replace( '/-nightly.*$/', '', $db_version ); + $base_current_version = preg_replace( '/-nightly.*$/', '', EE_VERSION ); + + if ( Comparator::lessThan( $base_current_version, $base_db_version ) ) { + + $ee_update_command = IS_DARWIN ? 'brew upgrade easyengine' : 'ee cli update --stable --yes'; + $ee_update_msg = sprintf( + 'It seems you\'re not running latest version. Update EasyEngine using `%s`.', + $ee_update_command + ); + + if ( ! empty( $this->arguments ) && 'cli' === $this->arguments[0] ) { + EE::warning( $ee_update_msg ); + } else { + EE::error( $ee_update_msg ); + } + } elseif ( $db_version !== $current_version ) { + EE::log( 'Executing migrations. This might take some time.' ); + $this->trigger_migration( $current_version ); + } elseif ( false !== strpos( $current_version, 'nightly' ) ) { + $this->trigger_migration( $current_version ); + } + } + + private function trigger_migration( $version ) { + if ( ! $this->migrate() ) { + EE::error( 'There was some error while migrating. Please check logs.' ); + } + if ( $version !== Option::get( 'version' ) ) { + Option::set( 'version', $version ); + \EE\Service\Utils\set_nginx_proxy_version_conf(); + } + } + + /** + * Get a suggestion on similar (sub)commands when the user entered an + * unknown (sub)command. + * + * @param string $entry User entry that didn't match an + * existing command. + * @param CompositeCommand $root_command Root command to start search for + * suggestions at. + * + * @return string Suggestion that fits the user entry, or an empty string. + */ + private function get_subcommand_suggestion( $entry, CompositeCommand $root_command = null ) { + $commands = array(); + $this->enumerate_commands( $root_command ?: \EE::get_root_command(), $commands ); + + return Utils\get_suggestion( $entry, $commands, $threshold = 2 ); + } + + /** + * Recursive method to enumerate all known commands. + * + * @param CompositeCommand $command Composite command to recurse over. + * @param array $list Reference to list accumulating results. + * @param string $parent Parent command to use as prefix. + */ + private function enumerate_commands( CompositeCommand $command, array &$list, $parent = '' ) { + foreach ( $command->get_subcommands() as $subcommand ) { + /** @var CompositeCommand $subcommand */ + $command_string = empty( $parent ) + ? $subcommand->get_name() + : "{$parent} {$subcommand->get_name()}"; + + $list[] = $command_string; + + $this->enumerate_commands( $subcommand, $list, $command_string ); + } + } + + /** + * Enables (almost) full PHP error reporting to stderr. + */ + private function enable_error_reporting() { + if ( E_ALL !== error_reporting() ) { + // Don't enable E_DEPRECATED as old versions of WP use PHP 4 style constructors and the mysql extension. + error_reporting( E_ALL & ~E_DEPRECATED ); + } + ini_set( 'display_errors', 'stderr' ); + } +} diff --git a/php/EE/SynopsisParser.php b/php/EE/SynopsisParser.php new file mode 100644 index 000000000..6d7a5531f --- /dev/null +++ b/php/EE/SynopsisParser.php @@ -0,0 +1,165 @@ +..." + * into [ optional=>false, type=>positional, repeating=>true, name=>object-id ] + */ +class SynopsisParser { + + /** + * @param string A synopsis + * @return array List of parameters + */ + public static function parse( $synopsis ) { + $tokens = array_filter( preg_split( '/[\s\t]+/', $synopsis ) ); + + $params = array(); + foreach ( $tokens as $token ) { + $param = self::classify_token( $token ); + + // Some types of parameters shouldn't be mandatory + if ( isset( $param['optional'] ) && ! $param['optional'] ) { + if ( 'flag' === $param['type'] || ( 'assoc' === $param['type'] && $param['value']['optional'] ) ) { + $param['type'] = 'unknown'; + } + } + + $param['token'] = $token; + $params[] = $param; + } + + return $params; + } + + /** + * @param array A structured synopsis + * @return string Rendered synopsis + */ + public static function render( $synopsis ) { + if ( ! is_array( $synopsis ) ) { + return ''; + } + $bits = array( + 'positional' => '', + 'assoc' => '', + 'generic' => '', + 'flag' => '', + ); + foreach ( $bits as $key => &$value ) { + foreach ( $synopsis as $arg ) { + if ( empty( $arg['type'] ) + || $key !== $arg['type'] ) { + continue; + } + + if ( empty( $arg['name'] ) && 'generic' !== $arg['type'] ) { + continue; + } + + if ( 'positional' === $key ) { + $rendered_arg = "<{$arg['name']}>"; + } elseif ( 'assoc' === $key ) { + $arg_value = isset( $arg['value']['name'] ) ? $arg['value']['name'] : $arg['name']; + $rendered_arg = "--{$arg['name']}=<{$arg_value}>"; + } elseif ( 'generic' === $key ) { + $rendered_arg = '--='; + } elseif ( 'flag' === $key ) { + $rendered_arg = "--{$arg['name']}"; + } + if ( ! empty( $arg['repeating'] ) ) { + $rendered_arg = "{$rendered_arg}..."; + } + if ( ! empty( $arg['optional'] ) ) { + $rendered_arg = "[{$rendered_arg}]"; + } + $value .= "{$rendered_arg} "; + } + } + $rendered = ''; + foreach ( $bits as $v ) { + if ( ! empty( $v ) ) { + $rendered .= $v; + } + } + return rtrim( $rendered, ' ' ); + } + + /** + * Classify argument attributes based on its syntax. + * + * @param string $token + * @return array $param + */ + private static function classify_token( $token ) { + $param = array(); + + list( $param['optional'], $token ) = self::is_optional( $token ); + list( $param['repeating'], $token ) = self::is_repeating( $token ); + + $p_name = '([a-z-_0-9]+)'; + $p_value = '([a-zA-Z-_|,0-9]+)'; + + if ( '--=' === $token ) { + $param['type'] = 'generic'; + } elseif ( preg_match( "/^<($p_value)>$/", $token, $matches ) ) { + $param['type'] = 'positional'; + $param['name'] = $matches[1]; + } elseif ( preg_match( "/^--(?:\\[no-\\])?$p_name/", $token, $matches ) ) { + $param['name'] = $matches[1]; + + $value = substr( $token, strlen( $matches[0] ) ); + + // substr returns false <= PHP 5.6, and '' PHP 7+ + if ( false === $value || '' === $value ) { + $param['type'] = 'flag'; + } else { + $param['type'] = 'assoc'; + + list( $param['value']['optional'], $value ) = self::is_optional( $value ); + + if ( preg_match( "/^=<$p_value>$/", $value, $matches ) ) { + $param['value']['name'] = $matches[1]; + } else { + $param = array( + 'type' => 'unknown', + ); + } + } + } else { + $param['type'] = 'unknown'; + } + + return $param; + } + + /** + * An optional parameter is surrounded by square brackets. + * + * @param string $token + * @return array + */ + private static function is_optional( $token ) { + if ( '[' == substr( $token, 0, 1 ) && ']' == substr( $token, -1 ) ) { + return array( true, substr( $token, 1, -1 ) ); + } + + return array( false, $token ); + } + + /** + * A repeating parameter is followed by an ellipsis. + * + * @param string $token + * @return array + */ + private static function is_repeating( $token ) { + if ( '...' === substr( $token, -3 ) ) { + return array( true, substr( $token, 0, -3 ) ); + } + + return array( false, $token ); + } +} diff --git a/php/EE/SynopsisValidator.php b/php/EE/SynopsisValidator.php new file mode 100644 index 000000000..b854f78d0 --- /dev/null +++ b/php/EE/SynopsisValidator.php @@ -0,0 +1,181 @@ +spec = SynopsisParser::parse( $synopsis ); + } + + /** + * Get any unknown arguments. + * + * @return array + */ + public function get_unknown() { + return array_column( + $this->query_spec( + array( + 'type' => 'unknown', + ) + ), 'token' + ); + } + + /** + * Check whether there are enough positional arguments. + * + * @param array $args Positional arguments. + * @return bool + */ + public function enough_positionals( $args ) { + $positional = $this->query_spec( + array( + 'type' => 'positional', + 'optional' => false, + ) + ); + + return count( $args ) >= count( $positional ); + } + + /** + * Check for any unknown positionals. + * + * @param array $args Positional arguments. + * @return array + */ + public function unknown_positionals( $args ) { + $positional_repeating = $this->query_spec( + array( + 'type' => 'positional', + 'repeating' => true, + ) + ); + + // At least one positional supports as many as possible. + if ( ! empty( $positional_repeating ) ) { + return array(); + } + + $positional = $this->query_spec( + array( + 'type' => 'positional', + 'repeating' => false, + ) + ); + + return array_slice( $args, count( $positional ) ); + } + + /** + * Check that all required keys are present and that they have values. + * + * @param array $assoc_args Parameters passed to command. + * @return array + */ + public function validate_assoc( $assoc_args ) { + $assoc_spec = $this->query_spec( + array( + 'type' => 'assoc', + ) + ); + + $errors = array( + 'fatal' => array(), + 'warning' => array(), + ); + + $to_unset = array(); + + foreach ( $assoc_spec as $param ) { + $key = $param['name']; + + if ( ! isset( $assoc_args[ $key ] ) ) { + if ( ! $param['optional'] ) { + $errors['fatal'][ $key ] = "missing --$key parameter"; + } + } else { + if ( true === $assoc_args[ $key ] && ! $param['value']['optional'] ) { + $error_type = ( ! $param['optional'] ) ? 'fatal' : 'warning'; + $errors[ $error_type ][ $key ] = "--$key parameter needs a value"; + + $to_unset[] = $key; + } + } + } + + return array( $errors, $to_unset ); + } + + /** + * Check whether there are unknown parameters supplied. + * + * @param array $assoc_args Parameters passed to command. + * @return array|false + */ + public function unknown_assoc( $assoc_args ) { + $generic = $this->query_spec( + array( + 'type' => 'generic', + ) + ); + + if ( count( $generic ) ) { + return array(); + } + + $known_assoc = array(); + + foreach ( $this->spec as $param ) { + if ( in_array( $param['type'], array( 'assoc', 'flag' ) ) ) { + $known_assoc[] = $param['name']; + } + } + + return array_diff( array_keys( $assoc_args ), $known_assoc ); + } + + /** + * Filters a list of associative arrays, based on a set of key => value arguments. + * + * @param array $args An array of key => value arguments to match against + * @param string $operator + * @return array + */ + private function query_spec( $args, $operator = 'AND' ) { + $operator = strtoupper( $operator ); + $count = count( $args ); + $filtered = array(); + + foreach ( $this->spec as $key => $to_match ) { + $matched = 0; + foreach ( $args as $m_key => $m_value ) { + if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] ) { + $matched++; + } + } + + if ( ( 'AND' == $operator && $matched == $count ) + || ( 'OR' == $operator && $matched > 0 ) + || ( 'NOT' == $operator && 0 == $matched ) ) { + $filtered[ $key ] = $to_match; + } + } + + return $filtered; + } + +} diff --git a/php/README.md b/php/README.md new file mode 100644 index 000000000..2ae430ce8 --- /dev/null +++ b/php/README.md @@ -0,0 +1,13 @@ +Internal-API +============ +This folder contains the main part of EasyEngine cli. + +* It conatins the `cli-command` which handles the most basic required functions like: `version, update` etc. + +* The [WP-CLI internal-api](https://github.com/wp-cli/handbook/blob/master/internal-api.md), booting and auto-loading logic and other utilities. + +* It also contains new classes: + * `EE_DB` having the generic Sqlite3 functions for db management. + * `EE_DOCKER` having various docker related functions like starting, stopping and inspecting containers, creating and connecting to networks, generating `docker-compose.yml`. + +* Also, the internal-api has been slightly modified to remove WordPress specific functions and additional features like, logging of all the command invocation and related log, success, debug and error messages from `EE::info(), EE::success(), EE::debug(), EE::error()` outputs into a file for debugging purposes. \ No newline at end of file diff --git a/php/boot-fs.php b/php/boot-fs.php new file mode 100644 index 000000000..9f7413c7c --- /dev/null +++ b/php/boot-fs.php @@ -0,0 +1,18 @@ +add_namespace( + 'EE\Bootstrap', + EE_ROOT . '/php/EE/Bootstrap' + )->register(); +} + +/** + * Initialize and return the bootstrap state to pass from step to step. + * + * @return BootstrapState + */ +function initialize_bootstrap_state() { + return new BootstrapState(); +} + +/** + * Process the bootstrapping steps. + * + * Loops over each of the provided steps, instantiates it and then calls its + * `process()` method. + */ +function bootstrap() { + prepare_bootstrap(); + $state = initialize_bootstrap_state(); + + foreach ( get_bootstrap_steps() as $step ) { + /** @var \EE\Bootstrap\BootstrapStep $step_instance */ + $step_instance = new $step(); + $state = $step_instance->process( $state ); + } +} diff --git a/php/class-ee-command.php b/php/class-ee-command.php new file mode 100644 index 000000000..484906e3b --- /dev/null +++ b/php/class-ee-command.php @@ -0,0 +1,12 @@ +select = ''; + $this->tables = ''; + $this->limit = ''; + $this->offset = ''; + $this->where = [ + 'query_string' => null, + 'bindings' => null, + ]; + } + + /** + * Function to initialize db and db connection. + */ + private static function init_db() { + + if ( ! ( file_exists( DB ) ) ) { + self::create_required_tables(); + + return; + } + + try { + self::$pdo = new PDO( 'sqlite:' . DB ); + self::$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + } catch ( PDOException $exception ) { + EE::error( 'Unable to initialize connection to EE sqlite database: ' . $exception->getMessage() ); + } + } + + /** + * Sqlite database creation. + */ + private static function create_required_tables() { + try { + self::$pdo = new PDO( 'sqlite:' . DB ); + self::$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + } catch ( PDOException $exception ) { + EE::error( $exception->getMessage() ); + } + + $query = 'CREATE TABLE migrations ( + id INTEGER, + migration VARCHAR, + type VARCHAR, + timestamp DATETIME, + PRIMARY KEY (id) + );'; + + $query .= 'CREATE TABLE options ( + key VARCHAR NOT NULL, + value VARCHAR NOT NULL, + PRIMARY KEY (key) + );'; + + try { + self::$pdo->exec( $query ); + } catch ( PDOException $exception ) { + EE::error( 'Encountered Error while creating table: ' . $exception->getMessage() ); + } + } + + /** + * Fetches first record from current query + * + * @throws Exception + * + * @return array Record + */ + public function first() { + $pdo_statement = $this->common_retrieval_function(); + + return $pdo_statement->fetch(); + } + + /** + * Common retrival function that runs current 'select' query. + * Other methods (like get and first) can use this to provide higher level functionality on top of it + * + * @throws Exception + * + * @return bool|PDOStatement + */ + private function common_retrieval_function() { + if ( null === $this->tables ) { + throw new Exception( 'Select: No table specified' ); + } + + $where = $this->where['query_string']; + + if ( empty( $this->select ) ) { + $this->select = '*'; + } + + $query = "SELECT $this->select FROM $this->tables{$where}{$this->limit}{$this->offset};"; + + $pdo_statement = self::$pdo->prepare( $query ); + $pdo_statement->setFetchMode( PDO::FETCH_ASSOC ); + + $bindings = $this->where['bindings'] ?? []; + + foreach ( $bindings as $key => $binding ) { + $pdo_statement->bindValue( $key + 1, $binding ); + } + + $result = $pdo_statement->execute(); + + if ( ! $result ) { + EE::debug( implode( ' ', self::$pdo->errorInfo() ) ); + + throw new PDOException( self::$pdo->errorInfo() ); + } + + return $pdo_statement; + } + + /** + * Adds where condition in query. + * + * i.e. where('id', 100) or where('id', '>', 100) + * or where([ + * [ 'id', '<', 100 ], + * [ 'name', 'ee' ] + * ]) + * or where([ + * 'id' => 100, + * 'name' => 'ee', + * ]) + * + * Supported operators are: '=', '<', '>', '<=', '>=', '==', '!=', '<>', 'like', 'in' + * + * @param ...$args One or more where condition. + * + * @throws Exception + * + * @return EE_DB + */ + public function where( ...$args ) { + $args = func_get_args(); + $conditions = []; + + if ( 'array' === gettype( $args[0] ) ) { + if ( \EE\Utils\is_assoc( $args[0] ) ) { + $condition_keys = array_keys( $args[0] ); + foreach ( $condition_keys as $key ) { + $conditions[] = $this->get_where_fragment( [ $key, $args[0][ $key ] ] ); + } + } else { + foreach ( $args[0] as $condition ) { + $conditions[] = $this->get_where_fragment( $condition ); + } + } + } else { + $conditions[] = $this->get_where_fragment( $args ); + } + + $this->where = [ + 'query_string' => ' WHERE ' . implode( ' AND ', array_column( $conditions, 'query_string' ) ), + 'bindings' => array_column( $conditions, 'binding' ), + ]; + + return $this; + } + + /** + * Returns a query fragment for where clause + * + * If the param given is ['column', 100], it returns ['column = ?', 100] + * If the param given is ['column', '>', 100], it returns ['column > ?', 100] + * + * @param array $condition An array of format [column, operator, value] or [column, value] + * + * @throws Exception + * + * @return array prepared query string and its corresponding binding + */ + private function get_where_fragment( array $condition ) { + + if ( empty( $condition ) || count( $condition ) > 3 ) { + throw new Exception( 'Where clause array must non empty with less than 3 elements' ); + } + + $column = $condition[0]; + $operator = '='; + + if ( 'string' !== gettype( $column ) ) { + throw new Exception( 'Where clause column must be of type string' ); + } + + if ( isset( $condition[2] ) ) { + $operator = $condition[1]; + $allowed_operators = [ '=', '<', '>', '<=', '>=', '==', '!=', '<>', 'like', 'in' ]; + + if ( ! in_array( strtolower( $operator ), $allowed_operators ) ) { + throw new Exception( 'Where clause operator should be in one of following: ' . implode( ' ', $allowed_operators ) ); + } + + $value = $condition[2]; + } elseif ( isset( $condition[1] ) ) { + $value = $condition[1]; + } else { + throw new Exception( 'Where clause value must be set' ); + } + + if ( 'string' !== gettype( $operator ) || ! in_array( gettype( $value ), [ 'string', 'integer', 'boolean' ], true ) ) { + throw new Exception( 'Where clause operator and value must be string' ); + } + + return [ + 'query_string' => "$column $operator ?", + 'binding' => $value, + ]; + } + + /** + * Select data from the database. + * + * @param array $args Columns to select + * + * @throws Exception If no tables are specified + * + * @return EE_DB + */ + public function select( ...$args ) { + + if ( empty( $args ) ) { + $columns = '*'; + } else { + $columns = implode( ', ', $args ); + } + + $this->select = $columns; + + return $this; + } + + /** + * Selects table to do operation on. + * + * @param ...$args Tables to run query on. + * + * @return EE_DB + */ + public function table( ...$args ) { + $this->tables = implode( ', ', $args ); + + return $this; + } + + /** + * Fetches all records from current query. + * + * @throws Exception + * + * @return array All records + */ + public function all() { + return $this->get(); + } + + /** + * Fetches all records from current query + * + * @throws Exception + * + * @return array All records + */ + public function get() { + $pdo_statement = $this->common_retrieval_function(); + + if ( ! $pdo_statement ) { + return false; + } + + return $pdo_statement->fetchAll(); + } + + /** + * Adds limit to query. + * + * @param int $limit Limit value. + * + * @return EE_DB + */ + public function limit( int $limit ) { + $this->limit = ' LIMIT ' . (string) $limit; + + return $this; + } + + /** + * Adds offset to query. + * + * @param int $offset Offset of query + * + * @return EE_DB + */ + public function offset( int $offset ) { + $this->offset = ' OFFSET ' . (string) $offset; + + return $this; + } + + /** + * Insert row in table. + * + * @param array $data in key value pair. + * + * @throws Exception If no table or more than one tables are specified + * + * @return bool + */ + public function insert( $data ) { + + $fields = implode( ', ', array_keys( $data ) ); + $values = implode( ', ', array_fill( 0, count( $data ), '?' ) ); + + if ( empty( $this->tables ) ) { + throw new Exception( 'Insert: No table specified' ); + } + + if ( strpos( $this->tables, ',' ) !== false ) { + throw new Exception( 'Insert: Multiple table specified' ); + } + + $query = "INSERT INTO $this->tables ($fields) VALUES ($values);"; + + $pdo_statement = self::$pdo->prepare( $query ); + $bindings = array_values( $data ); + + foreach ( $bindings as $key => $value ) { + $pdo_statement->bindValue( $key + 1, $value ); + } + + $result = $pdo_statement->execute(); + + if ( ! $result ) { + EE::debug( implode( ' ', self::$pdo->errorInfo() ) ); + + throw new PDOException( self::$pdo->errorInfo() ); + } + + return self::$pdo->lastInsertId(); + } + + /** + * Update row in table. + * + * @param array $values Associative array of columns and their values + * + * @throws Exception If no table are specified or multiple table are specified or no where clause are specified + * + * @return bool + */ + public function update( $values ) { + if ( empty( $this->tables ) ) { + throw new Exception( 'Update: No table specified' ); + } + + if ( empty( $this->where ) ) { + throw new Exception( 'Update: No where clause specified' ); + } + + if ( strpos( $this->tables, ',' ) !== false ) { + throw new Exception( 'Update: Multiple table specified' ); + } + + if ( empty( $values ) ) { + return false; + } + + $set_keys = array_keys( $values ); + $set_bindings = array_values( $values ); + $where_bindings = $this->where['bindings']; + + $set_clause = implode( ' = ?, ', $set_keys) . ' = ?'; + + $query = "UPDATE $this->tables SET $set_clause{$this->where['query_string']}"; + $pdo_statement = self::$pdo->query( $query ); + + $counter = 0; //We need counter here as we need to bind values of both SET and WHERE clauses + + foreach ( $set_bindings as $binding ) { + $pdo_statement->bindValue( ++ $counter, $binding ); + } + + foreach ( $where_bindings as $binding ) { + $pdo_statement->bindValue( ++ $counter, $binding ); + } + + $result = $pdo_statement->execute(); + + if ( ! $result ) { + EE::debug( implode( ' ', self::$pdo->errorInfo() ) ); + + throw new PDOException( self::$pdo->errorInfo() ); + } + + return true; + } + + /** + * Delete data from table. + * + * @throws Exception If no table are specified or multiple table are specified or no where clause are specified + * + * @return bool Success. + */ + public function delete() { + if ( empty( $this->tables ) ) { + throw new Exception( 'Delete: No table specified' ); + } + + if ( empty( $this->where ) ) { + throw new Exception( 'Delete: No where clause specified' ); + } + + if ( strpos( $this->tables, ',' ) !== false ) { + throw new Exception( 'Delete: Multiple table specified' ); + } + + $query = "DELETE FROM $this->tables{$this->where['query_string']}"; + + $pdo_statement = self::$pdo->query( $query ); + + foreach ( $this->where['bindings'] as $key => $binding ) { + $pdo_statement->bindValue( $key + 1, $binding ); + } + + $result = $pdo_statement->execute(); + + if ( ! $result ) { + EE::debug( implode( ' ', self::$pdo->errorInfo() ) ); + + throw new PDOException( self::$pdo->errorInfo() ); + } + + return true; + } +} diff --git a/php/class-ee-docker.php b/php/class-ee-docker.php new file mode 100644 index 000000000..d1deacbfb --- /dev/null +++ b/php/class-ee-docker.php @@ -0,0 +1,456 @@ +config['custom-compose']; + + if ( ! empty( $custom_compose ) ) { + $custom_compose_path = SITE_CUSTOM_DOCKER_COMPOSE_DIR . '/' . $custom_compose; + if ( SITE_CUSTOM_DOCKER_COMPOSE === $custom_compose ) { + if ( $fs->exists( SITE_CUSTOM_DOCKER_COMPOSE ) ) { + $custom_compose_path = SITE_CUSTOM_DOCKER_COMPOSE; + } + } + if ( $fs->exists( $custom_compose_path ) ) { + $command .= ' -f ' . $custom_compose_path; + } else { + EE::warning( 'File: ' . $custom_compose_path . ' does not exist. Falling back to default compose file.' ); + } + } else { + + if ( $fs->exists( SITE_CUSTOM_DOCKER_COMPOSE_DIR ) ) { + $ymlFiles = glob( SITE_CUSTOM_DOCKER_COMPOSE_DIR . '/*.yml' ); + $yamlFiles = glob( SITE_CUSTOM_DOCKER_COMPOSE_DIR . '/*.yaml' ); + + $custom_compose_files = array_merge( $ymlFiles, $yamlFiles ); + + $files_before_custom = array_unique( array_merge( $files_before_custom, $custom_compose_files ) ); + } + + foreach ( $files_before_custom as $file ) { + if ( $fs->exists( $file ) ) { + $command .= ' -f ' . $file; + } + } + + if ( $fs->exists( SITE_CUSTOM_DOCKER_COMPOSE ) ) { + $command .= ' -f ' . SITE_CUSTOM_DOCKER_COMPOSE; + } + } + + return $command; + } + + /** + * Check and Start or create container if not running. + * + * @param String $container Name of the container. + * @param String $command Command to launch that container if needed. + * + * @return bool success. + */ + public static function boot_container( $container, $command = '' ) { + + $status = self::container_status( $container ); + if ( $status ) { + if ( 'exited' === $status ) { + return self::start_container( $container ); + } else { + return true; + } + } else { + return EE::exec( $command ); + } + } + + public static function container_status( $container ) { + + $exec_command = 'which docker'; + exec( $exec_command, $out, $ret ); + EE::debug( 'COMMAND: ' . $exec_command ); + EE::debug( 'RETURN CODE: ' . $ret ); + if ( $ret ) { + EE::error( 'Docker is not installed. Please install Docker to run EasyEngine.' ); + } + $status = EE::launch( "docker inspect -f '{{.State.Running}}' $container" ); + if ( ! $status->return_code ) { + if ( preg_match( '/true/', $status->stdout ) ) { + return 'running'; + } else { + return 'exited'; + } + } + + return false; + } + + /** + * Function to start the container if it exists but is not running. + * + * @param String $container Container to be started. + * + * @return bool success. + */ + public static function start_container( $container ) { + + return EE::exec( "docker start $container" ); + } + + /** + * Function to stop a container + * + * @param String $container Container to be stopped. + * + * @return bool success. + */ + public static function stop_container( $container ) { + + return EE::exec( "docker stop $container" ); + } + + /** + * Function to restart a container + * + * @param String $container Container to be restarted. + * + * @return bool success. + */ + public static function restart_container( $container ) { + + return EE::exec( "docker restart $container" ); + } + + /** + * Create docker network. + * + * @param String $name Name of the network to be created. + * + * @return bool success. + */ + public static function create_network( $name ) { + + return EE::exec( "docker network create $name --label=org.label-schema.vendor=\"EasyEngine\" " ); + } + + /** + * Connect to given docker network. + * + * @param String $name Name of the network that has to be connected. + * @param String $connect_to Name of the network to which connection has to be established. + * + * @return bool success. + */ + public static function connect_network( $name, $connect_to ) { + + return EE::exec( "docker network connect $name $connect_to" ); + } + + /** + * Remove docker network. + * + * @param String $name Name of the network to be removed. + * + * @return bool success. + */ + public static function rm_network( $name ) { + + return EE::exec( "docker network rm $name" ); + } + + /** + * Disconnect docker network. + * + * @param String $name Name of the network to be disconnected. + * @param String $connected_to Name of the network from which it has to be disconnected. + * + * @return bool success. + */ + public static function disconnect_network( $name, $connected_to ) { + + return EE::exec( "docker network disconnect $name $connected_to" ); + } + + + /** + * Function to connect site network to appropriate containers. + */ + public static function connect_site_network_to( $site_name, $to_container ) { + + if ( self::connect_network( $site_name, $to_container ) ) { + EE::success( "Site connected to $to_container." ); + } else { + throw new Exception( "There was some error connecting to $to_container." ); + } + } + + /** + * Function to disconnect site network from appropriate containers. + */ + public static function disconnect_site_network_from( $site_name, $from_container ) { + + if ( self::disconnect_network( $site_name, $from_container ) ) { + EE::log( "[$site_name] Disconnected from Docker network of $from_container" ); + } else { + EE::warning( "Error in disconnecting from Docker network of $from_container" ); + } + } + + + /** + * Function to boot the containers. + * + * @param String $dir Path to docker-compose.yml. + * @param array $services Services to bring up. + * + * @return bool success. + */ + public static function docker_compose_up( $dir, $services = [] ) { + + $chdir_return_code = chdir( $dir ); + if ( $chdir_return_code ) { + if ( empty( $services ) ) { + return EE::exec( \EE_DOCKER::docker_compose_with_custom() . ' up -d' ); + } else { + $all_services = implode( ' ', $services ); + + return EE::exec( \EE_DOCKER::docker_compose_with_custom() . ' up -d ' . $all_services ); + } + } + + return false; + } + + /** + * Function to exec and run commands into the containers. + * + * @param string $command Command to exec. + * @param string $service Service to exec command into. + * @param string $shell Shell in which exec command will be executed. + * @param string $user User to execute command into. + * @param String $dir Path to docker-compose.yml. + * @param bool $shell_wrapper If shell wrapper should be enabled or not. + * @param bool $exit_on_error To exit or not on error. + * @param array $exec_obfuscate Data to be obfuscated from log. + * @param bool $echo_stdout Output stdout of exec if true. + * @param bool $echo_stderr Output stderr of exec if true. + * + * @return bool success. + */ + public static function docker_compose_exec( $command = '', $service = '', $shell = 'sh', $user = '', $dir = '', $shell_wrapper = false, $exit_on_error = false, $exec_obfuscate = [], $echo_stdout = false, $echo_stderr = false ) { + + if ( ! empty( $dir ) ) { + $chdir_return_code = chdir( $dir ); + } else { + $chdir_return_code = true; + } + + $skip_tty = \EE::get_runner()->config['skip-tty']; + $tty = empty( $skip_tty ) ? '' : '-T'; + + if ( $chdir_return_code ) { + + $user_string = ''; + if ( $user ) { + $user_string = empty( $user ) ? '' : "--user='$user'"; + } + + if ( $shell_wrapper ) { + return EE::exec( \EE_DOCKER::docker_compose_with_custom() . " exec $tty $user_string $service $shell -c \"$command\"", $echo_stdout, $echo_stderr, $exec_obfuscate, $exit_on_error ); + } else { + return EE::exec( \EE_DOCKER::docker_compose_with_custom() . " exec $tty $user_string $service $command", $echo_stdout, $echo_stderr, $exec_obfuscate, $exit_on_error ); + } + + } + + return false; + } + + /** + * Function to check if a network exists + * + * @param string $network Name/ID of network to check + * + * @return bool Network exists or not + */ + public static function docker_network_exists( string $network ) { + + return EE::exec( "docker network inspect $network" ); + } + + /** + * Function to destroy the containers. + * + * @param String $dir Path to docker-compose.yml. + * + * @return bool success. + */ + public static function docker_compose_down( $dir ) { + + $chdir_return_code = chdir( $dir ); + if ( $chdir_return_code ) { + + return EE::exec( \EE_DOCKER::docker_compose_with_custom() . ' down' ); + } + + return false; + } + + /** + * Check if a particular service exists in given docker-compose.yml. + * + * @param string $service Service whose availability needs to be checked. + * @param string $site_fs_path Path to the site root where docker-compose.yml file is present. + * + * @return bool Whether service is available or not. + */ + public static function service_exists( $service, $site_fs_path ) { + + return in_array( $service, \EE_DOCKER::get_services( $site_fs_path ), true ); + } + + /** + * Get list of all docker-compose services. + * + * @param string $site_fs_path Path to the site root where docker-compose.yml file is present. + * + * @return bool Whether service is available or not. + */ + public static function get_services( $site_fs_path = '' ) { + + if ( ! empty( $site_fs_path ) ) { + chdir( $site_fs_path ); + } + $custom_service = empty( \EE::get_runner()->config['custom-compose'] ) ? false : true; + $launch = EE::launch( \EE_DOCKER::docker_compose_with_custom( [], $custom_service ) . ' config --services' ); + $services = explode( PHP_EOL, trim( $launch->stdout ) ); + + return $services; + } + + /** + * Gets a dockerized prefix created for site. + * + * @param string $site_url Name of the site. + * + * @return string prefix derived from the name. + */ + public static function get_docker_style_prefix( $site_url ) { + + return str_replace( [ '.', '-' ], '', $site_url ); + } + + /** + * Function to create external docker volumes and related symlinks. + * + * @param string $prefix Prefix by volumes have to be created. + * @param array $volumes The volumes to be created. + * $volumes[$key]['name'] => specifies the name of volume to be created. + * $volumes[$key]['path_to_symlink'] => specifies the path to symlink the + * created volume. + * $volumes[$key]['skip_volume'] => if set to `true` will skip volume + * creation for that entry. + * @param bool $update_to_docker_prefix Update the prefix in dockerized style. + */ + public static function create_volumes( $prefix, $volumes, $update_to_docker_prefix = true ) { + + $volume_prefix = $update_to_docker_prefix ? self::get_docker_style_prefix( $prefix ) : $prefix; + $fs = new Filesystem(); + + // This command will get the root directory for Docker, generally `/var/lib/docker`. + $launch = EE::launch( "docker info 2> /dev/null | awk '/Docker Root Dir/ {print $4}'" ); + $docker_root_dir = trim( $launch->stdout ); + + foreach ( $volumes as $volume ) { + if ( ! empty( $volume['skip_volume'] ) && true === $volume['skip_volume'] ) { + continue; + } + $vol_check = EE::launch( 'docker volume inspect ' . $volume_prefix . '_' . $volume['name'] ); + // Skip if volume already exists. + if ( 0 === $vol_check->return_code ) { + continue; + } + $path_to_symlink_not_empty = ! empty( dirname( $volume['path_to_symlink'] ) ); + if ( $path_to_symlink_not_empty ) { + $fs->mkdir( dirname( $volume['path_to_symlink'] ) ); + } + EE::exec( + sprintf( + 'docker volume create \ + --label "org.label-schema.vendor=EasyEngine" \ + --label "io.easyengine.site=%s" \ + %s_%s', + $prefix, + $volume_prefix, + $volume['name'] + ) + ); + if ( $path_to_symlink_not_empty ) { + $fs->symlink( sprintf( '%s/volumes/%s_%s/_data', $docker_root_dir, $volume_prefix, $volume['name'] ), $volume['path_to_symlink'] ); + } + } + } + + /** + * Function to get all the volumes with a specific label. + * + * @param string $label The label to search for. + * + * @return array Found containers. + */ + public static function get_volumes_by_label( $label ) { + + $launch = EE::launch( sprintf( 'docker volume ls --filter="label=org.label-schema.vendor=EasyEngine" --filter="label=io.easyengine.site=%s" -q', $label ) ); + + return array_filter( explode( PHP_EOL, trim( $launch->stdout ) ), 'trim' ); + } + + /** + * Function to return minimal docker-compose `host:container` volume mounting array. + * + * @param array $extended_vols : + * $extended_vols['name'] - Host path for docker-compose generation in linux + * $extended_vols['path_to_symlink'] - Host path for docker-compose generation in + * darwin. + * $extended_vols['container_path'] - Path inside container, common for linux and + * darwin. + * $extended_vols['skip_darwin'] - if set to true skips that volume for darwin. + * $extended_vols['skip_linux'] - if set to true skips that volume for linux. + * + * @return array having docker-compose `host:container` volume mounting. + */ + public static function get_mounting_volume_array( $extended_vols ) { + + $volume_gen_key = IS_DARWIN ? 'path_to_symlink' : 'name'; + $skip_key = IS_DARWIN ? 'skip_darwin' : 'skip_linux'; + $final_mount_volumes = []; + foreach ( $extended_vols as $extended_vol ) { + if ( ! empty( $extended_vol[ $skip_key ] ) && true === $extended_vol[ $skip_key ] ) { + continue; + } + $final_mount_volumes[] = $extended_vol[ $volume_gen_key ] . ':' . $extended_vol['container_path']; + } + + return $final_mount_volumes; + } + +} diff --git a/php/class-ee.php b/php/class-ee.php new file mode 100644 index 000000000..e9dc1b0d3 --- /dev/null +++ b/php/class-ee.php @@ -0,0 +1,1190 @@ +clean(); + } + ); + } + } + + return $cache; + } + + /** + * Colorize a string for output. + * + * Yes, you too can change the color of command line text. For instance, + * here's how `EE::success()` colorizes "Success: " + * + * ``` + * EE::colorize( "%GSuccess:%n " ) + * ``` + * + * Uses `\cli\Colors::colorize()` to transform color tokens to display + * settings. Choose from the following tokens (and note 'reset'): + * + * * %y => ['color' => 'yellow'], + * * %g => ['color' => 'green'], + * * %b => ['color' => 'blue'], + * * %r => ['color' => 'red'], + * * %p => ['color' => 'magenta'], + * * %m => ['color' => 'magenta'], + * * %c => ['color' => 'cyan'], + * * %w => ['color' => 'grey'], + * * %k => ['color' => 'black'], + * * %n => ['color' => 'reset'], + * * %Y => ['color' => 'yellow', 'style' => 'bright'], + * * %G => ['color' => 'green', 'style' => 'bright'], + * * %B => ['color' => 'blue', 'style' => 'bright'], + * * %R => ['color' => 'red', 'style' => 'bright'], + * * %P => ['color' => 'magenta', 'style' => 'bright'], + * * %M => ['color' => 'magenta', 'style' => 'bright'], + * * %C => ['color' => 'cyan', 'style' => 'bright'], + * * %W => ['color' => 'grey', 'style' => 'bright'], + * * %K => ['color' => 'black', 'style' => 'bright'], + * * %N => ['color' => 'reset', 'style' => 'bright'], + * * %3 => ['background' => 'yellow'], + * * %2 => ['background' => 'green'], + * * %4 => ['background' => 'blue'], + * * %1 => ['background' => 'red'], + * * %5 => ['background' => 'magenta'], + * * %6 => ['background' => 'cyan'], + * * %7 => ['background' => 'grey'], + * * %0 => ['background' => 'black'], + * * %F => ['style' => 'blink'], + * * %U => ['style' => 'underline'], + * * %8 => ['style' => 'inverse'], + * * %9 => ['style' => 'bright'], + * * %_ => ['style' => 'bright'] + * + * @access public + * @category Output + * + * @param string $string String to colorize for output, with color tokens. + * @return string Colorized string. + */ + public static function colorize( $string ) { + return \cli\Colors::colorize( $string, self::get_runner()->in_color() ); + } + + /** + * Schedule a callback to be executed at a certain point. + * + * Hooks conceptually are very similar to WordPress actions. EE hooks + * are typically called before WordPress is loaded. + * + * EE hooks include: + * + * * `before_add_command:` - Before the command is added. + * * `after_add_command:` - After the command was added. + * * `before_invoke:` - Just before a command is invoked. + * * `after_invoke:` - Just after a command is invoked. + * * `find_command_to_run_pre` - Just before EE finds the command to run. + * + * EE commands can create their own hooks with `EE::do_hook()`. + * + * If additional arguments are passed through the `EE::do_hook()` call, + * these will be passed on to the callback provided by `EE::add_hook()`. + * + * + * @access public + * @category Registration + * + * @param string $when Identifier for the hook. + * @param mixed $callback Callback to execute when hook is called. + * @return null + */ + public static function add_hook( $when, $callback ) { + if ( array_key_exists( $when, self::$hooks_passed ) ) { + call_user_func_array( $callback, (array) self::$hooks_passed[ $when ] ); + } + + self::$hooks[ $when ][] = $callback; + } + + /** + * Execute callbacks registered to a given hook. + * + * See `EE::add_hook()` for details on EE's internal hook system. + * Commands can provide and call their own hooks. + * + * @access public + * @category Registration + * + * @param string $when Identifier for the hook. + * @param mixed ... Optional. Arguments that will be passed onto the + * callback provided by `EE::add_hook()`. + * @return null + */ + public static function do_hook( $when ) { + $args = func_num_args() > 1 + ? array_slice( func_get_args(), 1 ) + : array(); + + self::$hooks_passed[ $when ] = $args; + + if ( ! isset( self::$hooks[ $when ] ) ) { + return; + } + + foreach ( self::$hooks[ $when ] as $callback ) { + call_user_func_array( $callback, $args ); + } + } + + /** + * Register a command to EE. + * + * EE supports using any callable class, function, or closure as a + * command. `EE::add_command()` is used for both internal and + * third-party command registration. + * + * Command arguments are parsed from PHPDoc by default, but also can be + * supplied as an optional third argument during registration. + * + * ``` + * # Register a custom 'foo' command to output a supplied positional param. + * # + * # $ ee foo bar --append=qux + * # Success: bar qux + * + * /** + * * My awesome closure command + * * + * * + * * : An awesome message to display + * * + * * --append= + * * : An awesome message to append to the original message. + * *\/ + * $foo = function( $args, $assoc_args ) { + * EE::success( $args[0] . ' ' . $assoc_args['append'] ); + * }; + * EE::add_command( 'foo', $foo ); + * ``` + * + * @access public + * @category Registration + * + * @param string $name Name for the command (e.g. "post list" or "site empty"). + * @param callable $callable Command implementation as a class, function or closure. + * @param array $args { + * Optional. An associative array with additional registration parameters. + * + * @type callable $before_invoke Callback to execute before invoking the command. + * @type callable $after_invoke Callback to execute after invoking the command. + * @type string $shortdesc Short description (80 char or less) for the command. + * @type string $longdesc Description of arbitrary length for examples, etc. + * @type string $synopsis The synopsis for the command (string or array). + * @type string $when Execute callback on a named EE hook. + * @type bool $is_deferred Whether the command addition had already been deferred. + * } + * @return true True on success, false if deferred, hard error if registration failed. + */ + public static function add_command( $name, $callable, $args = array() ) { + // Bail immediately if the EE executable has not been run. + if ( ! defined( 'EE' ) ) { + return false; + } + + $valid = false; + if ( is_callable( $callable ) ) { + $valid = true; + } elseif ( is_string( $callable ) && class_exists( (string) $callable ) ) { + $valid = true; + } elseif ( is_object( $callable ) ) { + $valid = true; + } + if ( ! $valid ) { + if ( is_array( $callable ) ) { + $callable[0] = is_object( $callable[0] ) ? get_class( $callable[0] ) : $callable[0]; + $callable = array( $callable[0], $callable[1] ); + } + EE::error( sprintf( 'Callable %s does not exist, and cannot be registered as `ee %s`.', json_encode( $callable ), $name ) ); + } + + $addition = new Dispatcher\CommandAddition(); + self::do_hook( "before_add_command:{$name}", $addition ); + + if ( $addition->was_aborted() ) { + EE::warning( "Aborting the addition of the command '{$name}' with reason: {$addition->get_reason()}." ); + return false; + } + + foreach ( array( 'before_invoke', 'after_invoke' ) as $when ) { + if ( isset( $args[ $when ] ) ) { + self::add_hook( "{$when}:{$name}", $args[ $when ] ); + } + } + + $path = preg_split( '/\s+/', $name ); + + $leaf_name = array_pop( $path ); + $full_path = $path; + + $command = self::get_root_command(); + + while ( ! empty( $path ) ) { + $subcommand_name = $path[0]; + $parent = implode( ' ', $path ); + $subcommand = $command->find_subcommand( $path ); + + // Parent not found. Defer addition or create an empty container as + // needed. + if ( ! $subcommand ) { + if ( isset( $args['is_deferred'] ) && $args['is_deferred'] ) { + $subcommand = new Dispatcher\CompositeCommand( + $command, + $subcommand_name, + new \EE\DocParser( '' ) + ); + $command->add_subcommand( $subcommand_name, $subcommand ); + } else { + self::defer_command_addition( + $name, + $parent, + $callable, + $args + ); + + return false; + } + } + + $command = $subcommand; + } + + $leaf_command = Dispatcher\CommandFactory::create( $leaf_name, $callable, $command ); + + if ( $leaf_command instanceof Dispatcher\CommandNamespace && array_key_exists( $leaf_name, $command->get_subcommands() ) ) { + return false; + } + + if ( ! $command->can_have_subcommands() ) { + throw new Exception( + sprintf( + "'%s' can't have subcommands.", + implode( ' ' , Dispatcher\get_path( $command ) ) + ) + ); + } + + if ( isset( $args['shortdesc'] ) ) { + $leaf_command->set_shortdesc( $args['shortdesc'] ); + } + + if ( isset( $args['longdesc'] ) ) { + $leaf_command->set_longdesc( $args['longdesc'] ); + } + + if ( isset( $args['synopsis'] ) ) { + if ( is_string( $args['synopsis'] ) ) { + $leaf_command->set_synopsis( $args['synopsis'] ); + } elseif ( is_array( $args['synopsis'] ) ) { + $synopsis = \EE\SynopsisParser::render( $args['synopsis'] ); + $leaf_command->set_synopsis( $synopsis ); + $long_desc = ''; + $bits = explode( ' ', $synopsis ); + foreach ( $args['synopsis'] as $key => $arg ) { + $long_desc .= $bits[ $key ] . "\n"; + if ( ! empty( $arg['description'] ) ) { + $long_desc .= ': ' . $arg['description'] . "\n"; + } + $yamlify = array(); + foreach ( array( 'default', 'options' ) as $key ) { + if ( isset( $arg[ $key ] ) ) { + $yamlify[ $key ] = $arg[ $key ]; + } + } + if ( ! empty( $yamlify ) ) { + $long_desc .= Spyc::YAMLDump( $yamlify ); + $long_desc .= '---' . "\n"; + } + $long_desc .= "\n"; + } + if ( ! empty( $long_desc ) ) { + $long_desc = rtrim( $long_desc, "\r\n" ); + $long_desc = '## OPTIONS' . "\n\n" . $long_desc; + if ( ! empty( $args['longdesc'] ) ) { + $long_desc .= "\n\n" . ltrim( $args['longdesc'], "\r\n" ); + } + $leaf_command->set_longdesc( $long_desc ); + } + } + } + + if ( isset( $args['when'] ) ) { + self::get_runner()->register_early_invoke( $args['when'], $leaf_command ); + } + + $command->add_subcommand( $leaf_name, $leaf_command ); + + self::do_hook( "after_add_command:{$name}" ); + return true; + } + + /** + * Defer command addition for a sub-command if the parent command is not yet + * registered. + * + * @param string $name Name for the sub-command. + * @param string $parent Name for the parent command. + * @param string $callable Command implementation as a class, function or closure. + * @param array $args Optional. See `EE::add_command()` for details. + */ + private static function defer_command_addition( $name, $parent, $callable, $args = array() ) { + $args['is_deferred'] = true; + self::$deferred_additions[ $name ] = array( + 'parent' => $parent, + 'callable' => $callable, + 'args' => $args, + ); + self::add_hook( + "after_add_command:$parent", + function () use ( $name ) { + $deferred_additions = EE::get_deferred_additions(); + + if ( ! array_key_exists( $name, $deferred_additions ) ) { + return; + } + + $callable = $deferred_additions[ $name ]['callable']; + $args = $deferred_additions[ $name ]['args']; + EE::remove_deferred_addition( $name ); + + EE::add_command( $name, $callable, $args ); + } + ); + } + + /** + * Get the list of outstanding deferred command additions. + * + * @return array Array of outstanding command additions. + */ + public static function get_deferred_additions() { + return self::$deferred_additions; + } + + /** + * Remove a command addition from the list of outstanding deferred additions. + */ + public static function remove_deferred_addition( $name ) { + if ( ! array_key_exists( $name, self::$deferred_additions ) ) { + EE::warning( "Trying to remove a non-existent command addition '{$name}'." ); + } + + unset( self::$deferred_additions[ $name ] ); + } + + /** + * Display informational message without prefix. + * + * Should only be used for displaying sensitive info which should be skipped from log file. + * `EE::log()` is typically recommended for everything else. + * + * @access public + * @category Output + * + * @param string $message Message to display to the end user. + * + * @return null + */ + public static function line( $message = '' ) { + self::$logger->info( $message ); + } + + /** + * Display informational message without prefix. + * + * Message is written to STDOUT, or discarded when `--quiet` flag is supplied. + * + * ``` + * # `ee cli update` lets user know of each step in the update process. + * EE::log( sprintf( 'Downloading from %s...', $download_url ) ); + * ``` + * + * @access public + * @category Output + * + * @param string $message Message to write to STDOUT. + */ + public static function log( $message ) { + self::$logger->info( $message ); + self::$file_logger->info( $message ); + } + + /** + * Display success message prefixed with "Success: ". + * + * Success message is written to STDOUT. + * + * Typically recommended to inform user of successful script conclusion. + * + * @access public + * @category Output + * + * @param string $message Message to write to STDOUT. + * @return null + */ + public static function success( $message ) { + self::$logger->success( $message ); + } + + /** + * Display debug message prefixed with "Debug: " when `--debug` is used. + * + * Debug message is written to STDERR, and includes script execution time. + * + * Helpful for optionally showing greater detail when needed. Used throughout + * EE bootstrap process for easier debugging and profiling. + * + * @access public + * @category Output + * + * @param string $message Message to write to STDERR. + * @param string $group Organize debug message to a specific group. + * @return null + */ + public static function debug( $message, $group = false ) { + self::$logger->debug( self::error_to_string( $message ), $group ); + self::$file_logger->debug( $message ); + } + + /** + * Display warning message prefixed with "Warning: ". + * + * Warning message is written to STDERR. + * + * Use instead of `EE::debug()` when script execution should be permitted + * to continue. + * + * @access public + * @category Output + * + * @param string $message Message to write to STDERR. + * @return null + */ + public static function warning( $message ) { + self::$logger->warning( self::error_to_string( $message ) ); + self::$file_logger->warning( $message ); + } + + /** + * Display error message prefixed with "Error: " and exit script. + * + * Error message is written to STDERR. Defaults to halting script execution + * with return code 1. + * + * Use `EE::warning()` instead when script execution should be permitted + * to continue. + * + * @access public + * @category Output + * + * @param string|EE_Error $message Message to write to STDERR. + * @param boolean|integer $exit True defaults to exit(1). + * @return null + */ + public static function error( $message, $exit = true ) { + if ( ! isset( self::get_runner()->assoc_args['completions'] ) ) { + self::$logger->error( self::error_to_string( $message ) ); + self::$file_logger->error( $message ); + } + + $return_code = false; + if ( true === $exit ) { + $return_code = 1; + } elseif ( is_int( $exit ) && $exit >= 1 ) { + $return_code = $exit; + } + + if ( $return_code ) { + if ( self::$capture_exit ) { + throw new ExitException( null, $return_code ); + } + exit( $return_code ); + } + } + + /** + * Display error message prefixed with "Error: " and exit script without writing to log file. + * + * Use `EE::error()` instead when error is to be logged. + * + * @access public + * @category Output + * + * @param string|EE_Error $message Message to write to STDERR. + * @param boolean|integer $exit True defaults to exit(1). + * @return null + */ + public static function err( $message, $exit = true ) { + if ( ! isset( self::get_runner()->assoc_args['completions'] ) ) { + self::$logger->error( self::error_to_string( $message ) ); + } + + $return_code = false; + if ( true === $exit ) { + $return_code = 1; + } elseif ( is_int( $exit ) && $exit >= 1 ) { + $return_code = $exit; + } + + if ( $return_code ) { + if ( self::$capture_exit ) { + throw new ExitException( null, $return_code ); + } + exit( $return_code ); + } + } + + /** + * Halt script execution with a specific return code. + * + * Permits script execution to be overloaded by `EE::runcommand()` + * + * @access public + * @category Output + * + * @param integer $return_code + */ + public static function halt( $return_code ) { + if ( self::$capture_exit ) { + throw new ExitException( null, $return_code ); + } + exit( $return_code ); + } + + /** + * Display a multi-line error message in a red box. Doesn't exit script. + * + * Error message is written to STDERR. + * + * @access public + * @category Output + * + * @param array $message Multi-line error message to be displayed. + */ + public static function error_multi_line( $message_lines ) { + if ( ! isset( self::get_runner()->assoc_args['completions'] ) && is_array( $message_lines ) ) { + self::$logger->error_multi_line( array_map( array( __CLASS__, 'error_to_string' ), $message_lines ) ); + } + } + + /** + * Ask for confirmation before running a destructive operation. + * + * If 'y' is provided to the question, the script execution continues and returns true. If + * 'n' or any other response is provided to the question, script exits if + * $exit is set to true. Otherwise the script returns false. + * + * @access public + * @category Input + * + * @param string $question Question to display before the prompt. + * @param array $assoc_args Skips prompt if 'yes' is provided. + */ + public static function confirm( $question, $assoc_args = array(), $exit = true ) { + if ( ! \EE\Utils\get_flag_value( $assoc_args, 'yes' ) ) { + fwrite( STDOUT, $question . ' [y/n] ' ); + + $answer = strtolower( trim( fgets( STDIN ) ) ); + + if ( 'y' != $answer ) { + if( $exit === true ) + exit; + return false; + } + return true; + } + return true; + } + + /** + * Get user input for some question. + * + * @access public + * @category Input + * + * @param string $question Question to display before the prompt. + * + * @return string input from user. + */ + public static function input( $question ) { + fwrite( STDOUT, $question ); + + return strtolower( trim( fgets( STDIN ) ) ); + } + + /** + * Read value from a positional argument or from STDIN. + * + * @param array $args The list of positional arguments. + * @param int $index At which position to check for the value. + * + * @return string + */ + public static function get_value_from_arg_or_stdin( $args, $index ) { + if ( isset( $args[ $index ] ) ) { + $raw_value = $args[ $index ]; + } else { + // We don't use file_get_contents() here because it doesn't handle + // Ctrl-D properly, when typing in the value interactively. + $raw_value = ''; + while ( ( $line = fgets( STDIN ) ) !== false ) { + $raw_value .= $line; + } + } + + return $raw_value; + } + + /** + * Read a value, from various formats. + * + * @access public + * @category Input + * + * @param mixed $value + * @param array $assoc_args + */ + public static function read_value( $raw_value, $assoc_args = array() ) { + if ( \EE\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { + $value = json_decode( $raw_value, true ); + if ( null === $value ) { + EE::error( sprintf( 'Invalid JSON: %s', $raw_value ) ); + } + } else { + $value = $raw_value; + } + + return $value; + } + + /** + * Display a value, in various formats + * + * @param mixed $value Value to display. + * @param array $assoc_args Arguments passed to the command, determining format. + */ + public static function print_value( $value, $assoc_args = array() ) { + if ( \EE\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { + $value = json_encode( $value ); + } elseif ( \EE\Utils\get_flag_value( $assoc_args, 'format' ) === 'yaml' ) { + $value = Spyc::YAMLDump( $value, 2, 0 ); + } elseif ( is_array( $value ) || is_object( $value ) ) { + $value = var_export( $value ); + } + + echo $value . "\n"; + } + + /** + * Convert a EE_error into a string + * + * @param mixed $errors + * @return string + */ + public static function error_to_string( $errors ) { + if ( is_string( $errors ) ) { + return $errors; + } + + // Only json_encode() the data when it needs it + $render_data = function( $data ) { + if ( is_array( $data ) || is_object( $data ) ) { + return json_encode( $data ); + } + + return '"' . $data . '"'; + }; + + if ( is_object( $errors ) && is_a( $errors, 'EE_Error' ) ) { + foreach ( $errors->get_error_messages() as $message ) { + if ( $errors->get_error_data() ) { + return $message . ' ' . $render_data( $errors->get_error_data() ); + } + + return $message; + } + } + } + + /** + * Launch an arbitrary external process that takes over I/O. + * + * @access public + * @category Execution + * + * @param string $command External process to launch. + * @param boolean $exit_on_error Whether to exit if the command returns an elevated return code. + * @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results. + * @param array $obfuscate Parts of the command that need to be obfuscated. + * @param array $env Environment variables to set when running the command. + * @param string $cwd Directory to execute the command in. + * + * @return int|ProcessRun The command exit status, or a ProcessRun object for full details. + */ + public static function launch( $command, $exit_on_error = false, $return_detailed = true, $obfuscate = [], $env = null, $cwd = null ) { + Utils\check_proc_available( 'launch' ); + + $command_to_log = empty( $obfuscate ) ? $command : str_replace( $obfuscate, '******', $command ); + self::debug( '-----------------------' ); + self::debug( "COMMAND: $command_to_log" ); + + $proc = Process::create( $command, $cwd, $env ); + $results = $proc->run(); + + self::debug_run_command( $results ); + + if ( -1 == $results->return_code ) { + self::warning( "Spawned process returned exit code {$results->return_code}, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option." ); + } + + if ( $results->return_code && $exit_on_error ) { + exit( $results->return_code ); + } + + if ( $return_detailed ) { + return $results; + } + + return $results->return_code; + } + + /** + * Launch an arbitrary external process that takes over I/O. + * + * @access public + * @category Execution + * + * @param string $command External process to launch. + * @param bool $echo_stdout Print stdout to terminal. Default false. + * @param bool $echo_stderr Print stderr to terminal. Default false. + * @param array $obfuscate Parts of the command that need to be obfuscated. + * @param boolean $exit_on_error Exit if the command returns an elevated return code with stderr. + * + * @return bool True if executed successfully. False if failed. + */ + public static function exec( $command, $echo_stdout = false, $echo_stderr = false, $obfuscate = [], $exit_on_error = false ) { + Utils\check_proc_available( 'exec' ); + + $command_to_log = empty( $obfuscate ) ? $command : str_replace( $obfuscate, '******', $command ); + self::debug( '-----------------------' ); + self::debug( "COMMAND: $command_to_log" ); + + $proc = Process::create( $command, null, null ); + $results = $proc->run(); + + self::debug_run_command( $results ); + + if ( -1 == $results->return_code ) { + self::warning( "Spawned process returned exit code {$results->return_code}, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option." ); + } + + if ( $echo_stdout ) { + echo $results->stdout; + } + if ( $echo_stderr && ! $exit_on_error ) { + error_log( $results->stderr ); + } + if ( ! $results->return_code ) { + return true; + } + if ( $exit_on_error ) { + exit( $results->return_code ); + } + + return false; + + } + + /** + * Run a EE command in a new process reusing the current runtime arguments. + * + * Use `EE::runcommand()` instead, which is easier to use and works better. + * + * Note: While this command does persist a limited set of runtime arguments, + * it *does not* persist environment variables. Practically speaking, EE + * packages won't be loaded when using EE::launch_self() because the + * launched process doesn't have access to the current process $HOME. + * + * @access public + * @category Execution + * + * @param string $command EE command to call. + * @param array $args Positional arguments to include when calling the command. + * @param array $assoc_args Associative arguments to include when calling the command. + * @param bool $exit_on_error Whether to exit if the command returns an elevated return code. + * @param bool $return_detailed Whether to return an exit status (default) or detailed execution results. + * @param array $runtime_args Override one or more global args (path,url,user) + * @return int|ProcessRun The command exit status, or a ProcessRun instance + */ + public static function launch_self( $command, $args = array(), $assoc_args = array(), $exit_on_error = true, $return_detailed = false, $runtime_args = array() ) { + $php_bin = escapeshellarg( Utils\get_php_binary() ); + + $script_path = $GLOBALS['argv'][0]; + + if ( getenv( 'EE_CONFIG_PATH' ) ) { + $config_path = getenv( 'EE_CONFIG_PATH' ); + } else { + $config_path = EE_ROOT_DIR . '/config/config.yml'; + } + $config_path = escapeshellarg( $config_path ); + + $args = implode( ' ', array_map( 'escapeshellarg', $args ) ); + $assoc_args = \EE\Utils\assoc_args_to_str( $assoc_args ); + + $full_command = "EE_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$command} {$args} {$assoc_args}"; + + return self::launch( $full_command, $exit_on_error, $return_detailed ); + } + + /** + * Format and print debug messages for external command launch. + * + * @param Object $launch external command object. + */ + private static function debug_run_command( $launch ) { + if ( ! empty( $launch->stdout ) ) { + self::debug( "STDOUT: $launch->stdout" ); + } + if ( ! empty( $launch->stderr ) ) { + self::debug( "STDERR: $launch->stderr" ); + } + self::debug( "RETURN CODE: $launch->return_code" ); + self::debug( '-----------------------' ); + } + + /** + * Get the path to the PHP binary used when executing EE. + * + * Environment values permit specific binaries to be indicated. + * + * Note: moved to Utils, left for BC. + * + * @access public + * @category System + * + * @return string + */ + public static function get_php_binary() { + return Utils\get_php_binary(); + } + + /** + * Get values of global configuration parameters. + * + * Provides access to `--path=`, `--url=`, and other values of + * the [global configuration parameters](https://ee.org/config/). + * + * ``` + * EE::log( 'The --url= value is: ' . EE::get_config( 'url' ) ); + * ``` + * + * @access public + * @category Input + * + * @param string $key Get value for a specific global configuration parameter. + * @return mixed + */ + public static function get_config( $key = null ) { + if ( null === $key ) { + return self::get_runner()->config; + } + + if ( ! isset( self::get_runner()->config[ $key ] ) ) { + self::warning( "Unknown config option '$key'." ); + return null; + } + + return self::get_runner()->config[ $key ]; + } + + /** + * Run a EE command. + * + * Launches a new child process to run a specified EE command. + * Optionally: + * + * * Run the command in an existing process. + * * Prevent halting script execution on error. + * * Capture and return STDOUT, or full details about command execution. + * * Parse JSON output if the command rendered it. + * + * ``` + * $options = array( + * 'return' => true, // Return 'STDOUT'; use 'all' for full object. + * 'parse' => 'json', // Parse captured STDOUT to JSON array. + * 'launch' => false, // Reuse the current process. + * 'exit_error' => true, // Halt script execution on error. + * ); + * $plugins = EE::runcommand( 'plugin list --format=json', $options ); + * ``` + * + * @access public + * @category Execution + * + * @param string $command EE command to run, including arguments. + * @param array $options Configuration options for command execution. + * @return mixed + */ + public static function runcommand( $command, $options = array() ) { + $defaults = array( + 'launch' => true, // Launch a new process, or reuse the existing. + 'exit_error' => true, // Exit on error by default. + 'return' => false, // Capture and return output, or render in realtime. + 'parse' => false, // Parse returned output as a particular format. + ); + $options = array_merge( $defaults, $options ); + $launch = $options['launch']; + $exit_error = $options['exit_error']; + $return = $options['return']; + $parse = $options['parse']; + $retval = null; + if ( $launch ) { + Utils\check_proc_available( 'launch option' ); + + $descriptors = array( + 0 => STDIN, + 1 => STDOUT, + 2 => STDERR, + ); + + if ( $return ) { + $descriptors = array( + 0 => STDIN, + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ); + } + + $php_bin = escapeshellarg( Utils\get_php_binary() ); + $script_path = $GLOBALS['argv'][0]; + + // Persist runtime arguments unless they've been specified otherwise. + $configurator = \EE::get_configurator(); + $argv = array_slice( $GLOBALS['argv'], 1 ); + list( $_, $_, $runtime_config ) = $configurator->parse_args( $argv ); + foreach ( $runtime_config as $k => $v ) { + if ( preg_match( "|^--{$k}=?$|", $command ) ) { + unset( $runtime_config[ $k ] ); + } + } + $runtime_config = Utils\assoc_args_to_str( $runtime_config ); + + $runcommand = "{$php_bin} {$script_path} {$runtime_config} {$command}"; + + $proc = Utils\proc_open_compat( $runcommand, $descriptors, $pipes, getcwd() ); + + if ( $return ) { + $stdout = stream_get_contents( $pipes[1] ); + fclose( $pipes[1] ); + $stderr = stream_get_contents( $pipes[2] ); + fclose( $pipes[2] ); + } + $return_code = proc_close( $proc ); + if ( -1 == $return_code ) { + self::warning( 'Spawned process returned exit code -1, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option.' ); + } elseif ( $return_code && $exit_error ) { + exit( $return_code ); + } + if ( true === $return || 'stdout' === $return ) { + $retval = trim( $stdout ); + } elseif ( 'stderr' === $return ) { + $retval = trim( $stderr ); + } elseif ( 'return_code' === $return ) { + $retval = $return_code; + } elseif ( 'all' === $return ) { + $retval = (object) array( + 'stdout' => trim( $stdout ), + 'stderr' => trim( $stderr ), + 'return_code' => $return_code, + ); + } + } else { + $configurator = self::get_configurator(); + $argv = Utils\parse_str_to_argv( $command ); + list( $args, $assoc_args, $runtime_config ) = $configurator->parse_args( $argv ); + if ( $return ) { + $existing_logger = self::$logger; + self::$logger = new EE\Loggers\Execution; + self::$logger->ob_start(); + } + if ( ! $exit_error ) { + self::$capture_exit = true; + } + try { + self::get_runner()->run_command( + $args, $assoc_args, array( + 'back_compat_conversions' => true, + ) + ); + $return_code = 0; + } catch ( ExitException $e ) { + $return_code = $e->getCode(); + } + if ( $return ) { + $execution_logger = self::$logger; + $execution_logger->ob_end(); + self::$logger = $existing_logger; + $stdout = $execution_logger->stdout; + $stderr = $execution_logger->stderr; + if ( true === $return || 'stdout' === $return ) { + $retval = trim( $stdout ); + } elseif ( 'stderr' === $return ) { + $retval = trim( $stderr ); + } elseif ( 'return_code' === $return ) { + $retval = $return_code; + } elseif ( 'all' === $return ) { + $retval = (object) array( + 'stdout' => trim( $stdout ), + 'stderr' => trim( $stderr ), + 'return_code' => $return_code, + ); + } + } + if ( ! $exit_error ) { + self::$capture_exit = false; + } + } + if ( ( true === $return || 'stdout' === $return ) + && 'json' === $parse ) { + $retval = json_decode( $retval, true ); + } + return $retval; + } + + /** + * Run a given command within the current process using the same global + * parameters. + * + * Use `EE::runcommand()` instead, which is easier to use and works better. + * + * To run a command using a new process with the same global parameters, + * use EE::launch_self(). To run a command using a new process with + * different global parameters, use EE::launch(). + * + * ``` + * ob_start(); + * EE::run_command( array( 'cli', 'cmd-dump' ) ); + * $ret = ob_get_clean(); + * ``` + * + * @access public + * @category Execution + * + * @param array $args Positional arguments including command name. + * @param array $assoc_args + */ + public static function run_command( $args, $assoc_args = array() ) { + self::get_runner()->run_command( $args, $assoc_args ); + } + + public static function db() { + return new EE_DB(); + } +} diff --git a/php/commands/cli.php b/php/commands/cli.php new file mode 100644 index 000000000..786b4eb75 --- /dev/null +++ b/php/commands/cli.php @@ -0,0 +1,7 @@ +...] + * : Get help on a specific command. + * + * ## EXAMPLES + * + * # get help for `site` command + * ee help site + * + * # get help for `site create` subcommand + * ee help site create + */ + public function __invoke( $args, $assoc_args ) { + $r = EE::get_runner()->find_command_to_run( $args ); + + if ( is_array( $r ) ) { + list( $command ) = $r; + + self::show_help( $command ); + exit; + } + } + + private static function show_help( $command ) { + $out = self::get_initial_markdown( $command ); + + // Remove subcommands if in columns - will wordwrap separately. + $subcommands = ''; + $column_subpattern = '[ \t]+[^\t]+\t+'; + if ( preg_match( '/(^## SUBCOMMANDS[^\n]*\n+' . $column_subpattern . '.+?)(?:^##|\z)/ms', $out, $matches, PREG_OFFSET_CAPTURE ) ) { + $subcommands = $matches[1][0]; + $subcommands_header = "## SUBCOMMANDS\n"; + $out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) ); + } + + $out .= self::parse_reference_links( $command->get_longdesc() ); + + // definition lists + $out = preg_replace_callback( '/([^\n]+)\n: (.+?)(\n\n|$)/s', array( __CLASS__, 'rewrap_param_desc' ), $out ); + + // Ensure lines with no leading whitespace that aren't section headers are indented. + $out = preg_replace( '/^((?! |\t|##).)/m', "\t$1", $out ); + + $tab = str_repeat( ' ', 2 ); + + // Need to de-tab for wordwrapping to work properly. + $out = str_replace( "\t", $tab, $out ); + + $wordwrap_width = \cli\Shell::columns(); + + // Wordwrap with indent. + $out = preg_replace_callback( + '/^( *)([^\n]+)\n/m', + function ( $matches ) use ( $wordwrap_width ) { + return $matches[1] . str_replace( "\n", "\n{$matches[1]}", wordwrap( $matches[2], $wordwrap_width - strlen( $matches[1] ) ) ) . "\n"; + }, + $out + ); + + if ( $subcommands ) { + // Wordwrap with column indent. + $subcommands = preg_replace_callback( + '/^(' . $column_subpattern . ')([^\n]+)\n/m', + function ( $matches ) use ( $wordwrap_width, $tab ) { + // Need to de-tab for wordwrapping to work properly. + $matches[1] = str_replace( "\t", $tab, $matches[1] ); + $matches[2] = str_replace( "\t", $tab, $matches[2] ); + $padding_len = strlen( $matches[1] ); + $padding = str_repeat( ' ', $padding_len ); + return $matches[1] . str_replace( "\n", "\n$padding", wordwrap( $matches[2], $wordwrap_width - $padding_len ) ) . "\n"; + }, + $subcommands + ); + + // Put subcommands back. + $out = str_replace( $subcommands_header, $subcommands, $out ); + } + + // section headers + $out = preg_replace( '/^## ([A-Z ]+)/m', EE::colorize( '%9\1%n' ), $out ); + + self::pass_through_pager( $out ); + } + + private static function rewrap_param_desc( $matches ) { + $param = $matches[1]; + $desc = self::indent( "\t\t", $matches[2] ); + return "\t$param\n$desc\n\n"; + } + + private static function indent( $whitespace, $text ) { + $lines = explode( "\n", $text ); + foreach ( $lines as &$line ) { + $line = $whitespace . $line; + } + return implode( "\n", $lines ); + } + + private static function pass_through_pager( $out ) { + + if ( ! Utils\check_proc_available( null /*context*/, true /*return*/ ) ) { + EE::debug( 'Warning: check_proc_available() failed in pass_through_pager().', 'help' ); + return $out; + } + + if ( false === ( $pager = getenv( 'PAGER' ) ) ) { + $pager = Utils\is_windows() ? 'more' : 'less -r'; + } + + // For Windows 7 need to set code page to something other than Unicode (65001) to get around "Not enough memory." error with `more.com` on PHP 7.1+. + if ( 'more' === $pager && defined( 'PHP_WINDOWS_VERSION_MAJOR' ) && PHP_WINDOWS_VERSION_MAJOR < 10 && function_exists( 'sapi_windows_cp_set' ) ) { + // Note will also apply to Windows 8 (see http://msdn.microsoft.com/en-us/library/windows/desktop/ms724832.aspx) but probably harmless anyway. + $cp = getenv( 'EE_WINDOWS_CODE_PAGE' ) ?: 1252; // Code page 1252 is the most used so probably the most compat. + sapi_windows_cp_set( $cp ); // `sapi_windows_cp_set()` introduced PHP 7.1. + } + + // convert string to file handle + $fd = fopen( 'php://temp', 'r+b' ); + fwrite( $fd, $out ); + rewind( $fd ); + + $descriptorspec = array( + 0 => $fd, + 1 => STDOUT, + 2 => STDERR, + ); + + return proc_close( Utils\proc_open_compat( $pager, $descriptorspec, $pipes ) ); + } + + private static function get_initial_markdown( $command ) { + $name = implode( ' ', Dispatcher\get_path( $command ) ); + + $binding = array( + 'name' => $name, + 'shortdesc' => $command->get_shortdesc(), + ); + + $binding['synopsis'] = "$name " . $command->get_synopsis(); + + $alias = $command->get_alias(); + if ( $alias ) { + $binding['alias'] = $alias; + } + + if ( $command->can_have_subcommands() ) { + $binding['has-subcommands']['subcommands'] = self::render_subcommands( $command ); + } + + return Utils\mustache_render( 'man.mustache', $binding ); + } + + private static function render_subcommands( $command ) { + $subcommands = array(); + foreach ( $command->get_subcommands() as $subcommand ) { + + if ( EE::get_runner()->is_command_disabled( $subcommand ) ) { + continue; + } + + $subcommands[ $subcommand->get_name() ] = $subcommand->get_shortdesc(); + } + + $max_len = self::get_max_len( array_keys( $subcommands ) ); + + $lines = array(); + foreach ( $subcommands as $name => $desc ) { + $lines[] = str_pad( $name, $max_len ) . "\t\t\t" . $desc; + } + + return $lines; + } + + private static function get_max_len( $strings ) { + $max_len = 0; + foreach ( $strings as $str ) { + $len = strlen( $str ); + if ( $len > $max_len ) { + $max_len = $len; + } + } + + return $max_len; + } + + /** + * Parse reference links from longdescription. + * + * @param string $longdesc The longdescription from the `$command->get_longdesc()`. + * @return string The longdescription which has links as footnote. + */ + private static function parse_reference_links( $longdesc ) { + $description = ''; + foreach ( explode( "\n", $longdesc ) as $line ) { + if ( 0 === strpos( $line, '#' ) ) { + break; + } + $description .= $line . "\n"; + } + + // Fires if it has description text at the head of `$longdesc`. + if ( $description ) { + $links = array(); // An array of URLs from the description. + $pattern = '/\[.+?\]\((https?:\/\/.+?)\)/'; + $newdesc = preg_replace_callback( $pattern, function( $matches ) use ( &$links ) { + static $count = 0; + $count++; + $links[] = $matches[1]; + return str_replace( '(' . $matches[1] . ')', '[' . $count . ']', $matches[0] ); + }, $description ); + + $footnote = ''; + for ( $i = 0; $i < count( $links ); $i++ ) { + $n = $i + 1; + $footnote .= '[' . $n . '] ' . $links[ $i ] . "\n"; + } + + if ( $footnote ) { + $newdesc = trim( $newdesc ) . "\n\n---\n" . $footnote; + $longdesc = str_replace( trim( $description ), trim( $newdesc ), $longdesc ); + } + } + + return $longdesc; + } +} + +EE::add_command( 'help', 'Help_Command' ); + diff --git a/php/commands/src/CLI_Command.php b/php/commands/src/CLI_Command.php new file mode 100644 index 000000000..a4f5f6071 --- /dev/null +++ b/php/commands/src/CLI_Command.php @@ -0,0 +1,647 @@ + $command->get_name(), + 'description' => $command->get_shortdesc(), + 'longdesc' => $command->get_longdesc(), + ); + + foreach ( $command->get_subcommands() as $subcommand ) { + $dump['subcommands'][] = $this->command_to_array( $subcommand ); + } + + if ( empty( $dump['subcommands'] ) ) { + $dump['synopsis'] = (string) $command->get_synopsis(); + } + + return $dump; + } + + /** + * Print EE version. + * + * ## EXAMPLES + * + * # Display CLI version. + * $ ee cli version + * EE 0.24.1 + */ + public function version() { + EE::line( 'EE ' . EE_VERSION ); + } + + /** + * Print various details about the EE environment. + * + * Helpful for diagnostic purposes, this command shares: + * + * * OS information. + * * Shell information. + * * PHP binary used. + * * PHP binary version. + * * php.ini configuration file used (which is typically different than web). + * * EE root dir: where EE is installed (if non-Phar install). + * * EE global config: where the global config YAML file is located. + * * EE project config: where the project config YAML file is located. + * * EE version: currently installed version. + * + * See [config docs](https://ee.org/config/) for more details on global + * and project config YAML files. + * + * ## OPTIONS + * + * [--format=] + * : Render output in a particular format. + * --- + * default: list + * options: + * - list + * - json + * --- + * + * ## EXAMPLES + * + * # Display various data about the CLI environment. + * $ ee cli info + * OS: Linux 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64 + * Shell: /usr/bin/zsh + * PHP binary: /usr/bin/php + * PHP version: 7.1.12-1+ubuntu16.04.1+deb.sury.org+1 + * php.ini used: /etc/php/7.1/cli/php.ini + * EE root dir: phar://ee.phar + * EE packages dir: /home/person/.ee/packages/ + * EE global config: + * EE project config: + * EE version: 1.5.0 + */ + public function info( $_, $assoc_args ) { + $php_bin = Utils\get_php_binary(); + + $system_os = sprintf( '%s %s %s %s', php_uname( 's' ), php_uname( 'r' ), php_uname( 'v' ), php_uname( 'm' ) ); + $shell = getenv( 'SHELL' ); + if ( ! $shell && Utils\is_windows() ) { + $shell = getenv( 'ComSpec' ); + } + $runner = EE::get_runner(); + + $packages_dir = $runner->get_packages_dir_path(); + if ( ! is_dir( $packages_dir ) ) { + $packages_dir = null; + } + if ( \EE\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { + $info = array( + 'php_binary_path' => $php_bin, + 'global_config_path' => $runner->global_config_path, + 'project_config_path' => $runner->project_config_path, + 'ee_dir_path' => EE_ROOT, + 'ee_packages_dir_path' => $packages_dir, + 'ee_version' => EE_VERSION, + 'system_os' => $system_os, + 'shell' => $shell, + ); + + EE::line( json_encode( $info ) ); + } else { + + $info = array( + array( 'OS', $system_os ), + array( 'Shell', $shell ), + array( 'PHP binary', $php_bin ), + array( 'PHP version', PHP_VERSION ), + array( 'php.ini used', get_cfg_var( 'cfg_file_path' ) ), + array( 'EE root dir', EE_ROOT ), + array( 'EE vendor dir', EE_VENDOR_DIR ), + array( 'EE phar path', ( defined( 'EE_PHAR_PATH' ) ? EE_PHAR_PATH : '' ) ), + array( 'EE packages dir', $packages_dir ), + array( 'EE global config', $runner->global_config_path ), + array( 'EE project config', $runner->project_config_path ), + array( 'EE version', EE_VERSION ), + ); + + $info_table = new \cli\Table(); + $info_table->setRows( $info ); + $info_table->setRenderer( new \cli\table\Ascii() ); + $lines = array_slice( $info_table->getDisplayLines(), 2 ); + foreach ( $lines as $line ) { + \EE::line( $line ); + } + } + } + + /** + * Check to see if there is a newer version of EE available. + * + * Queries the Github releases API. Returns available versions if there are + * updates available, or success message if using the latest release. + * + * ## OPTIONS + * + * [--patch] + * : Only list patch updates. + * + * [--minor] + * : Only list minor updates. + * + * [--major] + * : Only list major updates. + * + * [--field=] + * : Prints the value of a single field for each update. + * + * [--fields=] + * : Limit the output to specific object fields. Defaults to version,update_type,package_url. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: table + * options: + * - table + * - csv + * - json + * - count + * - yaml + * --- + * + * ## EXAMPLES + * + * # Check for update. + * $ ee cli check-update + * Success: EE is at the latest version. + * + * # Check for update and new version is available. + * $ ee cli check-update + * +---------+-------------+------------------------------------------------------------------------------------------+ + * | version | update_type | package_url | + * +---------+-------------+------------------------------------------------------------------------------------------+ + * | 0.24.1 | patch | https://github.com/EasyEngine/easyengine/releases/download/v4.0.0-beta.1/easyengine.phar | + * +---------+-------------+------------------------------------------------------------------------------------------+ + * + * @subcommand check-update + */ + public function check_update( $_, $assoc_args ) { + $updates = $this->get_updates( $assoc_args ); + + if ( $updates ) { + $formatter = new \EE\Formatter( + $assoc_args, + array( 'version', 'update_type', 'package_url' ) + ); + $formatter->display_items( $updates ); + } elseif ( empty( $assoc_args['format'] ) || 'table' == $assoc_args['format'] ) { + $update_type = $this->get_update_type_str( $assoc_args ); + EE::success( "EasyEngine is at the latest{$update_type}version." ); + } + } + + /** + * Update EE to the latest release. + * + * Default behavior is to check the releases API for the newest stable + * version, and prompt if one is available. + * + * Use `--stable` to install or reinstall the latest stable version. + * + * Use `--nightly` to install the latest built version of the develop branch. + * While not recommended for production, nightly contains the latest and + * greatest, and should be stable enough for development and staging + * environments. + * + * ## OPTIONS + * + * [--stable] + * : Update to the latest stable release. + * + * [--nightly] + * : Update to the latest built version of the develop branch. Potentially unstable. + * + * [--yes] + * : Do not prompt for confirmation. + * + * ## EXAMPLES + * + * # Update CLI. + * You have version 0.24.0. Would you like to update to 0.24.1? [y/n] y + * Downloading from https://github.com/ee/ee/releases/download/v4.0.0/ee-4.0.0.phar... + * $ ee cli update/download/v0.24.1/ee-0.24.1.phar... + * New version works. Proceeding to replace. + * Success: Updated EE to 0.24.1. + */ + public function update( $_, $assoc_args ) { + + if ( ! Utils\inside_phar() ) { + EE::error( 'You can only self-update Phar files.' ); + } + if ( IS_DARWIN ) { + EE::error( 'Please use `brew upgrade easyengine` to update EasyEngine on macOS.' ); + } + $old_phar = realpath( $_SERVER['argv'][0] ); + if ( ! is_writable( $old_phar ) ) { + EE::error( sprintf( '%s is not writable by current user.', $old_phar ) ); + } elseif ( ! is_writable( dirname( $old_phar ) ) ) { + EE::error( sprintf( '%s is not writable by current user.', dirname( $old_phar ) ) ); + } + EE::confirm( "Note: 1. It is recommended to run EasyEngine update in tmux/screen.\n2. Have minimum 5GB free disk space.\nUpdate at times may take some time.\n\nTo view progress, tail logs in a different window using `tail -f /opt/easyengine/logs/ee.log`.\n\nAre you sure you want to continue?", $assoc_args ); + if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) { + EE::confirm( sprintf( 'You have version %s. Would you like to update to the latest nightly?', EE_VERSION ), $assoc_args ); + $download_url = 'https://raw.githubusercontent.com/EasyEngine/easyengine-builds/master/phar/easyengine-nightly.phar'; + $md5_url = 'https://raw.githubusercontent.com/EasyEngine/easyengine-builds/master/phar/easyengine-nightly.phar.md5'; + } elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) { + EE::confirm( sprintf( 'You have version %s. Would you like to update to the latest stable release?', EE_VERSION ), $assoc_args ); + $download_url = 'https://raw.githubusercontent.com/EasyEngine/easyengine-builds/master/phar/easyengine.phar'; + $md5_url = 'https://raw.githubusercontent.com/EasyEngine/easyengine-builds/master/phar/easyengine.phar.md5'; + } else { + $updates = $this->get_updates( $assoc_args ); + if ( empty( $updates ) ) { + $update_type = $this->get_update_type_str( $assoc_args ); + EE::success( "EasyEngine is at the latest{$update_type}version." ); + return; + } + $newest = $updates[0]; + EE::confirm( sprintf( 'You have version %s. Would you like to update to %s?', EE_VERSION, $newest['version'] ), $assoc_args ); + $download_url = $newest['package_url']; + $md5_url = str_replace( '.phar', '.phar.md5', $download_url ); + } + EE::get_runner()->check_requirements(); + EE::log( sprintf( 'Downloading from %s...', $download_url ) ); + $temp = \EE\Utils\get_temp_dir() . uniqid( 'ee_', true ) . '.phar'; + $headers = array(); + $options = array( + 'timeout' => 600, // 10 minutes ought to be enough for everybody. + 'filename' => $temp, + ); + Utils\http_request( 'GET', $download_url, null, $headers, $options ); + $md5_response = Utils\http_request( 'GET', $md5_url ); + if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { + EE::error( "Couldn't access md5 hash for release (HTTP code {$md5_response->status_code})." ); + } + $md5_file = md5_file( $temp ); + $release_hash = trim( $md5_response->body ); + if ( $md5_file === $release_hash ) { + EE::log( 'md5 hash verified: ' . $release_hash ); + } else { + EE::error( "md5 hash for download ({$md5_file}) is different than the release hash ({$release_hash})." ); + } + + EE::log( 'Updating EasyEngine to new version. This might take some time.' ); + + $php_binary = Utils\get_php_binary(); + $process = EE\Process::create( "{$php_binary} $temp cli info", null, null ); + $result = $process->run(); + if ( 0 !== $result->return_code || false === stripos( $result->stdout, 'EE version' ) ) { + $multi_line = explode( PHP_EOL, $result->stderr ); + EE::error_multi_line( $multi_line ); + EE::error( 'The downloaded PHAR is broken, try running ee cli update again.' ); + } + EE::log( 'New version works. Proceeding to replace.' ); + $mode = fileperms( $old_phar ) & 511; + if ( false === chmod( $temp, $mode ) ) { + EE::error( sprintf( 'Cannot chmod %s.', $temp ) ); + } + class_exists( '\cli\Streams' ); // This autoloads \cli\Streams - after we move the file we no longer have access to this class. + class_exists( '\cli\Colors' ); // This autoloads \cli\Colors + if ( false === rename( $temp, $old_phar ) ) { + EE::error( sprintf( 'Cannot move %s to %s', $temp, $old_phar ) ); + } + if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) { + $updated_version = 'the latest nightly release'; + } elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) { + $updated_version = 'the latest stable release'; + } else { + $updated_version = $newest['version']; + } + EE::success( sprintf( 'Updated EE to %s.', $updated_version ) ); + } + + /** + * Returns update information. + */ + private function get_updates( $assoc_args ) { + $url = 'https://api.github.com/repos/EasyEngine/easyengine/releases?per_page=100'; + $options = array( + 'timeout' => 30, + ); + $headers = array( + 'Accept' => 'application/json', + ); + if ( $github_token = getenv( 'GITHUB_TOKEN' ) ) { + $headers['Authorization'] = 'token ' . $github_token; + } + $response = Utils\http_request( 'GET', $url, null, $headers, $options ); + if ( ! $response->success || 200 !== $response->status_code ) { + EE::error( sprintf( 'Failed to get latest version (HTTP code %d).', $response->status_code ) ); + } + $release_data = json_decode( $response->body ); + $updates = array( + 'major' => false, + 'minor' => false, + 'patch' => false, + ); + foreach ( $release_data as $release ) { + // Get rid of leading "v" if there is one set. + $release_version = $release->tag_name; + if ( 'v' === substr( $release_version, 0, 1 ) ) { + $release_version = ltrim( $release_version, 'v' ); + } + $update_type = Utils\get_named_sem_ver( $release_version, EE_VERSION ); + if ( ! $update_type ) { + continue; + } + if ( ! empty( $updates[$update_type] ) && ! Comparator::greaterThan( $release_version, $updates[$update_type]['version'] ) ) { + continue; + } + $updates[$update_type] = array( + 'version' => $release_version, + 'update_type' => $update_type, + 'package_url' => $release->assets[0]->browser_download_url, + ); + } + foreach ( $updates as $type => $value ) { + if ( empty( $value ) ) { + unset( $updates[$type] ); + } + } + foreach ( array( 'major', 'minor', 'patch' ) as $type ) { + if ( true === \EE\Utils\get_flag_value( $assoc_args, $type ) ) { + return ! empty( $updates[$type] ) ? array( $updates[$type] ) : false; + } + } + if ( empty( $updates ) && preg_match( '#-alpha-(.+)$#', EE_VERSION, $matches ) ) { + $version_url = 'https://raw.githubusercontent.com/EasyEngine/easyengine-builds/master/phar/NIGHTLY_VERSION'; + $response = Utils\http_request( 'GET', $version_url ); + if ( ! $response->success || 200 !== $response->status_code ) { + EE::error( sprintf( 'Failed to get current nightly version (HTTP code %d)', $response->status_code ) ); + } + $nightly_version = trim( $response->body ); + if ( EE_VERSION != $nightly_version ) { + $updates['nightly'] = array( + 'version' => $nightly_version, + 'update_type' => 'nightly', + 'package_url' => 'https://raw.githubusercontent.com/EasyEngine/easyengine-builds/master/phar/easyengine-nightly.phar', + ); + } + } + + return array_values( $updates ); + } + + /** + * Dump the list of global parameters, as JSON or in var_export format. + * + * ## OPTIONS + * + * [--with-values] + * : Display current values also. + * + * [--format=] + * : Render output in a particular format. + * --- + * default: json + * options: + * - var_export + * - json + * --- + * + * ## EXAMPLES + * + * # Dump the list of global parameters. + * $ ee cli param-dump --format=var_export + * array ( + * 'path' => + * array ( + * 'runtime' => '=', + * 'file' => '', + * 'synopsis' => '', + * 'default' => NULL, + * 'multiple' => false, + * 'desc' => 'Path to the WordPress files.', + * ), + * 'url' => + * array ( + * + * @subcommand param-dump + */ + public function param_dump( $_, $assoc_args ) { + $spec = \EE::get_configurator()->get_spec(); + + if ( \EE\Utils\get_flag_value( $assoc_args, 'with-values' ) ) { + $config = \EE::get_configurator()->to_array(); + // Copy current config values to $spec + foreach ( $spec as $key => $value ) { + $current = null; + if ( isset( $config[0][$key] ) ) { + $current = $config[0][$key]; + } + $spec[$key]['current'] = $current; + } + } + + if ( 'var_export' === \EE\Utils\get_flag_value( $assoc_args, 'format' ) ) { + var_export( $spec ); + } else { + echo json_encode( $spec ); + } + } + + /** + * Dump the list of installed commands, as JSON. + * + * ## EXAMPLES + * + * # Dump the list of installed commands. + * $ ee cli cmd-dump + * {"name":"ee","description":"Manage WordPress through the command-line.","longdesc":"\n\n## GLOBAL PARAMETERS\n\n --path=\n Path to the WordPress files.\n\n --ssh=\n Perform operation against a remote server over SSH (or a container using scheme of "docker" or "docker-compose").\n\n --url=\n Pretend request came from given URL. In multisite, this argument is how the target site is specified. \n\n --user=\n + * + * @subcommand cmd-dump + */ + public function cmd_dump() { + echo json_encode( $this->command_to_array( EE::get_root_command() ) ); + } + + /** + * Uninstalls easyengine completely along with all sites + * + * ## OPTIONS + * + * [--yes] + * : Do not prompt for confirmation. + * + * @subcommand self-uninstall + */ + public function self_uninstall( $args, $assoc_args ) { + + if ( ! EE::get_runner()->check_requirements( false ) ) { + EE::error( 'Unable to proceed with uninstallation. Seems there is a dependency down.', false ); + die; + } + + EE::confirm( "Are you sure you want to remove EasyEngine and all its sites(along with their data)?\nThis is an irreversible action. No backup will be kept.", $assoc_args ); + + EE::exec( 'docker rm -f $(docker ps -aqf label=org.label-schema.vendor="EasyEngine")' ); + EE::exec( 'docker network rm $(docker network ls -f "label=org.label-schema.vendor=EasyEngine" --format="{{.Name}}")' ); + EE::exec( 'docker volume rm -f $(docker volume ls -f "label=org.label-schema.vendor=EasyEngine" -q)' ); + EE::exec( 'docker image rm $(docker image ls -f "label=org.label-schema.vendor=EasyEngine" -q)' ); + + $records = Site::all( [ 'site_fs_path' ] ); + + $fs = new Filesystem(); + if ( ! empty( $records ) ) { + $sites_paths = array_column( $records, 'site_fs_path' ); + $fs->remove( $sites_paths ); + } + + $fs->remove( EE_ROOT_DIR ); + + if ( Utils\inside_phar() ) { + unlink( realpath( $_SERVER['argv'][0] ) ); + } + } + + /** + * Generate tab completion strings. + * + * ## OPTIONS + * + * --line= + * : The current command line to be executed. + * + * --point= + * : The index to the current cursor position relative to the beginning of the command. + * + * [--shell=] + * : Shell for completions output. + * --- + * default: bash + * options: + * - bash + * - zsh + * --- + + * ## EXAMPLES + * + * # Generate tab completion strings. + * $ ee cli completions --line='ee eva' --point=100 + * eval + * eval-file + */ + public function completions( $_, $assoc_args ) { + $line = substr( $assoc_args['line'], 0, $assoc_args['point'] ); + $compl = new \EE\Completions( $line, $assoc_args['shell'] ); + $compl->render(); + } + + /** + * List available EE aliases. + * + * Aliases are shorthand references to WordPress installs. For instance, + * `@dev` could refer to a development install and `@prod` could refer to + * a production install. This command gives you visibility in what + * registered aliases you have available. + * + * ## OPTIONS + * + * [--format=] + * : Render output in a particular format. + * --- + * default: yaml + * options: + * - yaml + * - json + * --- + * + * ## EXAMPLES + * + * # List all available aliases. + * $ ee cli alias + * --- + * @all : Run command against every registered alias. + * @prod : + * ssh: runcommand@runcommand.io~/webapps/production + * @dev : + * ssh: vagrant@192.168.50.10/srv/www/runcommand.dev + * @both : + * - @prod + * - @dev + * + * @alias aliases + */ + public function alias( $_, $assoc_args ) { + EE::print_value( EE::get_runner()->aliases, $assoc_args ); + } + + /** + * Get a string representing the type of update being checked for. + */ + private function get_update_type_str( $assoc_args ) { + $update_type = ' '; + foreach ( array( 'major', 'minor', 'patch' ) as $type ) { + if ( true === \EE\Utils\get_flag_value( $assoc_args, $type ) ) { + $update_type = ' ' . $type . ' '; + break; + } + } + + return $update_type; + } + + /** + * Detects if a command exists + * + * This commands checks if a command is registered with EE. + * If the command is found then it returns with exit status 0. + * If the command doesn't exist, then it will exit with status 1. + * + * ## OPTIONS + * ... + * : The command + * + * ## EXAMPLES + * + * # The "site delete" command is registered. + * $ ee cli has-command "site delete" + * $ echo $? + * 0 + * + * # The "foo bar" command is not registered. + * $ ee cli has-command "foo bar" + * $ echo $? + * 1 + * + * @subcommand has-command + */ + public function has_command( $_, $assoc_args ) { + + // If command is input as a string, then explode it into array. + $command = explode( ' ', implode( ' ', $_ ) ); + + EE::halt( is_array( EE::get_runner()->find_command_to_run( $command ) ) ? 0 : 1 ); + } +} diff --git a/php/config-spec.php b/php/config-spec.php new file mode 100644 index 000000000..6baafa840 --- /dev/null +++ b/php/config-spec.php @@ -0,0 +1,96 @@ + array( + 'file' => '', + 'default' => array(), + 'desc' => '(Sub)commands to disable.', + ), + + 'sites_path' => array( + 'runtime' => '=', + 'file' => '', + 'default' => null, + 'desc' => 'Absolute path to where all sites will be stored.', + ), + + 'locale' => array( + 'runtime' => '=', + 'file' => '', + 'default' => null, + 'desc' => 'Locale for WordPress.', + ), + + 'le-mail' => array( + 'runtime' => '=', + 'file' => '', + 'default' => null, + 'desc' => 'Mail-id to be used for letsencrypt.', + ), + + 'env' => array( + 'runtime' => '=', + 'file' => '', + 'default' => null, + 'desc' => 'EasyEngine server environment.', + ), + + 'wp-mail' => array( + 'runtime' => '=', + 'file' => '', + 'default' => null, + 'desc' => 'Default Mail-id to be used for WordPress site installation.', + ), + + 'sysctl' => array( + 'runtime' => '=', + 'file' => '', + 'default' => false, + 'desc' => 'Whether to add sysctl config in docker-compose.', + ), + + 'skip-tty' => array( + 'runtime' => '=', + 'file' => '', + 'default' => false, + 'desc' => 'Skip tty allocation for remote command execution.', + ), + + 'custom-compose' => array( + 'runtime' => '=', + 'file' => '', + 'default' => null, + 'desc' => 'Path to custom compose file.', + ), + + 'ee_installer_version' => array( + 'file' => '', + 'default' => null, + 'desc' => 'EE version to run.', + ), + + 'color' => array( + 'runtime' => true, + 'file' => '', + 'default' => 'auto', + 'desc' => 'Whether to colorize the output.', + ), + + 'debug' => array( + 'runtime' => '[=]', + 'file' => '', + 'default' => false, + 'desc' => 'Show all PHP errors; add verbosity to EE bootstrap.', + ), + + 'quiet' => array( + 'runtime' => '', + 'file' => '', + 'default' => false, + 'desc' => 'Suppress informational messages.', + ), + +); diff --git a/php/dispatcher.php b/php/dispatcher.php new file mode 100644 index 000000000..7ef1c9bd7 --- /dev/null +++ b/php/dispatcher.php @@ -0,0 +1,20 @@ +get_name() ); + } while ( $command = $command->get_parent() ); + + return $path; +} + diff --git a/php/init-ee.php b/php/init-ee.php new file mode 100644 index 000000000..f5574d9b3 --- /dev/null +++ b/php/init-ee.php @@ -0,0 +1,27 @@ +config ) && ! empty( $composer->config->{'vendor-dir'} ) ) { + array_unshift( $vendor_paths, EE_ROOT . '/../../../' . $composer->config->{'vendor-dir'} ); + } + } + return $vendor_paths; +} + +// Using require() directly inside a class grants access to private methods to the loaded code +function load_file( $path ) { + require_once $path; +} + +function load_command( $name ) { + $path = EE_ROOT . "/php/commands/$name.php"; + + if ( is_readable( $path ) ) { + include_once $path; + } +} + +/** + * Like array_map(), except it returns a new iterator, instead of a modified array. + * + * Example: + * + * $arr = array('Football', 'Socker'); + * + * $it = iterator_map($arr, 'strtolower', function($val) { + * return str_replace('foo', 'bar', $val); + * }); + * + * foreach ( $it as $val ) { + * var_dump($val); + * } + * + * @param array|object Either a plain array or another iterator + * @param callback The function to apply to an element + * @return object An iterator that applies the given callback(s) + */ +function iterator_map( $it, $fn ) { + if ( is_array( $it ) ) { + $it = new \ArrayIterator( $it ); + } + + if ( ! method_exists( $it, 'add_transform' ) ) { + $it = new Transform( $it ); + } + + foreach ( array_slice( func_get_args(), 1 ) as $fn ) { + $it->add_transform( $fn ); + } + + return $it; +} + +/** + * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true + * @param string|array The files (or file) to search for + * @param string|null The directory to start searching from; defaults to CWD + * @param callable Function which is passed the current dir each time a directory level is traversed + * @return null|string Null if the file was not found + */ +function find_file_upward( $files, $dir = null, $stop_check = null ) { + $files = (array) $files; + if ( is_null( $dir ) ) { + $dir = getcwd(); + } + while ( is_readable( $dir ) ) { + // Stop walking up when the supplied callable returns true being passed the $dir + if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) { + return null; + } + + foreach ( $files as $file ) { + $path = $dir . DIRECTORY_SEPARATOR . $file; + if ( file_exists( $path ) ) { + return $path; + } + } + + $parent_dir = dirname( $dir ); + if ( empty( $parent_dir ) || $parent_dir === $dir ) { + break; + } + $dir = $parent_dir; + } + return null; +} + +function is_path_absolute( $path ) { + // Windows + if ( isset( $path[1] ) && ':' === $path[1] ) { + return true; + } + + return '/' === $path[0]; +} + +/** + * Composes positional arguments into a command string. + * + * @param array + * @return string + */ +function args_to_str( $args ) { + return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) ); +} + +/** + * Composes associative arguments into a command string. + * + * @param array + * @return string + */ +function assoc_args_to_str( $assoc_args ) { + $str = ''; + + foreach ( $assoc_args as $key => $value ) { + if ( true === $value ) { + $str .= " --$key"; + } elseif ( is_array( $value ) ) { + foreach ( $value as $_ => $v ) { + $str .= assoc_args_to_str( + array( + $key => $v, + ) + ); + } + } else { + $str .= " --$key=" . escapeshellarg( $value ); + } + } + + return $str; +} + +/** + * Given a template string and an arbitrary number of arguments, + * returns the final command, with the parameters escaped. + */ +function esc_cmd( $cmd ) { + if ( func_num_args() < 2 ) { + trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING ); + } + + $args = func_get_args(); + + $cmd = array_shift( $args ); + + return vsprintf( $cmd, array_map( 'escapeshellarg', $args ) ); +} + +/** + * Render a collection of items as an ASCII table, JSON, CSV, YAML, list of ids, or count. + * + * Given a collection of items with a consistent data structure: + * + * ``` + * $items = array( + * array( + * 'key' => 'foo', + * 'value' => 'bar', + * ) + * ); + * ``` + * + * Render `$items` as an ASCII table: + * + * ``` + * EE\Utils\format_items( 'table', $items, array( 'key', 'value' ) ); + * + * # +-----+-------+ + * # | key | value | + * # +-----+-------+ + * # | foo | bar | + * # +-----+-------+ + * ``` + * + * Or render `$items` as YAML: + * + * ``` + * EE\Utils\format_items( 'yaml', $items, array( 'key', 'value' ) ); + * + * # --- + * # - + * # key: foo + * # value: bar + * ``` + * + * @access public + * @category Output + * + * @param string $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count' + * @param array $items An array of items to output. + * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list. + * @return null + */ +function format_items( $format, $items, $fields ) { + $assoc_args = compact( 'format', 'fields' ); + $formatter = new \EE\Formatter( $assoc_args ); + $formatter->display_items( $items ); +} + +/** + * Write data as CSV to a given file. + * + * @access public + * + * @param resource $fd File descriptor + * @param array $rows Array of rows to output + * @param array $headers List of CSV columns (optional) + */ +function write_csv( $fd, $rows, $headers = array() ) { + if ( ! empty( $headers ) ) { + fputcsv( $fd, $headers ); + } + + foreach ( $rows as $row ) { + if ( ! empty( $headers ) ) { + $row = pick_fields( $row, $headers ); + } + + fputcsv( $fd, array_values( $row ) ); + } +} + +/** + * Pick fields from an associative array or object. + * + * @param array|object Associative array or object to pick fields from + * @param array List of fields to pick + * @return array + */ +function pick_fields( $item, $fields ) { + $item = (object) $item; + + $values = array(); + + foreach ( $fields as $field ) { + $values[ $field ] = isset( $item->$field ) ? $item->$field : null; + } + + return $values; +} + +/** + * Launch system's $EDITOR for the user to edit some text. + * + * @access public + * @category Input + * + * @param string $content Some form of text to edit (e.g. post content) + * @return string|bool Edited text, if file is saved from editor; false, if no change to file. + */ +function launch_editor_for_input( $input, $filename = 'EE' ) { + + check_proc_available( 'launch_editor_for_input' ); + + $tmpdir = get_temp_dir(); + + do { + $tmpfile = basename( $filename ); + $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); + $tmpfile .= '-' . substr( md5( mt_rand() ), 0, 6 ); + $tmpfile = $tmpdir . $tmpfile . '.tmp'; + $fp = fopen( $tmpfile, 'xb' ); + if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { + $tmpfile = ''; + continue; + } + if ( $fp ) { + fclose( $fp ); + } + } while ( ! $tmpfile ); + + if ( ! $tmpfile ) { + \EE::error( 'Error creating temporary file.' ); + } + + $output = ''; + file_put_contents( $tmpfile, $input ); + + $editor = getenv( 'EDITOR' ); + if ( ! $editor ) { + $editor = is_windows() ? 'notepad' : 'vi'; + } + + $descriptorspec = array( STDIN, STDOUT, STDERR ); + $process = proc_open_compat( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes ); + $r = proc_close( $process ); + if ( $r ) { + exit( $r ); + } + + $output = file_get_contents( $tmpfile ); + + unlink( $tmpfile ); + + if ( $output === $input ) { + return false; + } + + return $output; +} + +/** + * Render PHP or other types of files using Mustache templates. + * + * IMPORTANT: Automatic HTML escaping is disabled! + */ +function mustache_render( $template_name, $data = array() ) { + if ( ! file_exists( $template_name ) ) { + $template_name = EE_ROOT . "/templates/$template_name"; + } + + $template = file_get_contents( $template_name ); + + $m = new \Mustache_Engine( + array( + 'escape' => function ( $val ) { + return $val; }, + ) + ); + + return $m->render( $template, $data ); +} + +/** + * Create a progress bar to display percent completion of a given operation. + * + * Progress bar is written to STDOUT, and disabled when command is piped. Progress + * advances with `$progress->tick()`, and completes with `$progress->finish()`. + * Process bar also indicates elapsed time and expected total time. + * + * @access public + * @category Output + * + * @param string $message Text to display before the progress bar. + * @param integer $count Total number of ticks to be performed. + * @param int $interval Optional. The interval in milliseconds between updates. Default 100. + * @return cli\progress\Bar|EE\NoOp + */ +function make_progress_bar( $message, $count, $interval = 100 ) { + if ( \cli\Shell::isPiped() ) { + return new \EE\NoOp; + } + + return new \cli\progress\Bar( $message, $count, $interval ); +} + +/** + * Checks if an array is associative array + * + * @param array $arr array to check + * + * @return bool + */ +function is_assoc( $arr ) { + + $is_assoc = false; + + foreach ( $arr as $key => $value ) { + if ( is_string( $key ) ) { + $is_assoc = true; + break; + } + } + + return $is_assoc; +} + +function parse_url( $url ) { + $url_parts = \parse_url( $url ); + + if ( ! isset( $url_parts['scheme'] ) ) { + $url_parts = parse_url( 'http://' . $url ); + } + + return $url_parts; +} + +/** + * Check if we're running in a Windows environment (cmd.exe). + * + * @return bool + */ +function is_windows() { + return false !== ( $test_is_windows = getenv( 'EE_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN'; +} + +/** + * Replace magic constants in some PHP source code. + * + * @param string $source The PHP code to manipulate. + * @param string $path The path to use instead of the magic constants + */ +function replace_path_consts( $source, $path ) { + $replacements = array( + '__FILE__' => "'$path'", + '__DIR__' => "'" . dirname( $path ) . "'", + ); + + $old = array_keys( $replacements ); + $new = array_values( $replacements ); + + return str_replace( $old, $new, $source ); +} + +/** + * Make a HTTP request to a remote URL. + * + * Wraps the Requests HTTP library to ensure every request includes a cert. + * + * @access public + * + * @param string $method HTTP method (GET, POST, DELETE, etc.) + * @param string $url URL to make the HTTP request to. + * @param array $headers Add specific headers to the request. + * @param array $data + * @param array $options + * @return object + */ +function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { + + $cert_path = '/rmccue/requests/certificates/cacert.pem'; + $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; + if ( inside_phar() ) { + // cURL can't read Phar archives + $options['verify'] = extract_from_phar( + EE_VENDOR_DIR . $cert_path + ); + } else { + foreach ( get_vendor_paths() as $vendor_path ) { + if ( file_exists( $vendor_path . $cert_path ) ) { + $options['verify'] = $vendor_path . $cert_path; + break; + } + } + if ( empty( $options['verify'] ) ) { + $error_msg = 'Cannot find SSL certificate.'; + if ( $halt_on_error ) { + EE::error( $error_msg ); + } + throw new \RuntimeException( $error_msg ); + } + } + + try { + // Updated class name: \WpOrg\Requests\Requests + $response = \WpOrg\Requests\Requests::request( $url, $headers, $data, $method, $options ); + return $response; + } catch ( \WpOrg\Requests\Exception $ex ) { // Updated exception class name + + // Handle SSL certificate issues gracefully + $curl_error = false; + if ( $ex instanceof \WpOrg\Requests\Exception\Transport\Curl ) { + $curl_error = true; + } + + if ( $curl_error && in_array( $ex->getCode(), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ), true ) ) { + \EE::warning( sprintf( "Re-trying without verify after failing to get verified url '%s' %s.", $url, $ex->getMessage() ) ); + $options['verify'] = false; + try { + return \WpOrg\Requests\Requests::request( $url, $headers, $data, $method, $options ); + } catch ( \WpOrg\Requests\Exception $ex ) { + $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ); + if ( $halt_on_error ) { + EE::error( $error_msg ); + } + throw new \RuntimeException( $error_msg, null, $ex ); + } + } else { + $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ); + if ( $halt_on_error ) { + EE::error( $error_msg ); + } + throw new \RuntimeException( $error_msg, null, $ex ); + } + } +} + +/** + * Increments a version string using the "x.y.z-pre" format + * + * Can increment the major, minor or patch number by one + * If $new_version == "same" the version string is not changed + * If $new_version is not a known keyword, it will be used as the new version string directly + * + * @param string $current_version + * @param string $new_version + * @return string + */ +function increment_version( $current_version, $new_version ) { + // split version assuming the format is x.y.z-pre + $current_version = explode( '-', $current_version, 2 ); + $current_version[0] = explode( '.', $current_version[0] ); + + switch ( $new_version ) { + case 'same': + // do nothing + break; + + case 'patch': + $current_version[0][2]++; + + $current_version = array( $current_version[0] ); // drop possible pre-release info + break; + + case 'minor': + $current_version[0][1]++; + $current_version[0][2] = 0; + + $current_version = array( $current_version[0] ); // drop possible pre-release info + break; + + case 'major': + $current_version[0][0]++; + $current_version[0][1] = 0; + $current_version[0][2] = 0; + + $current_version = array( $current_version[0] ); // drop possible pre-release info + break; + + default: // not a keyword + $current_version = array( array( $new_version ) ); + break; + } + + // reconstruct version string + $current_version[0] = implode( '.', $current_version[0] ); + $current_version = implode( '-', $current_version ); + + return $current_version; +} + +/** + * Compare two version strings to get the named semantic version. + * + * @access public + * + * @param string $new_version + * @param string $original_version + * @return string $name 'major', 'minor', 'patch' + */ +function get_named_sem_ver( $new_version, $original_version ) { + + if ( ! Comparator::greaterThan( $new_version, $original_version ) ) { + return ''; + } + + $parts = explode( '-', $original_version ); + $bits = explode( '.', $parts[0] ); + $major = $bits[0]; + if ( isset( $bits[1] ) ) { + $minor = $bits[1]; + } + if ( isset( $bits[2] ) ) { + $patch = $bits[2]; + } + + if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { + return 'patch'; + } + + if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { + return 'minor'; + } + + return 'major'; +} + +/** + * Return the flag value or, if it's not set, the $default value. + * + * Because flags can be negated (e.g. --no-quiet to negate --quiet), this + * function provides a safer alternative to using + * `isset( $assoc_args['quiet'] )` or similar. + * + * @access public + * @category Input + * + * @param array $assoc_args Arguments array. + * @param string $flag Flag to get the value. + * @param mixed $default Default value for the flag. Default: NULL + * @return mixed + */ +function get_flag_value( $assoc_args, $flag, $default = null ) { + return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; +} + +/** + * Get the home directory. + * + * @access public + * @category System + * + * @return string + */ +function get_home_dir() { + $home = getenv( 'HOME' ); + if ( ! $home ) { + // In Windows $HOME may not be defined + $home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); + } + + return rtrim( $home, '/\\' ); +} + +/** + * Appends a trailing slash. + * + * @access public + * @category System + * + * @param string $string What to add the trailing slash to. + * @return string String with trailing slash added. + */ +function trailingslashit( $string ) { + return rtrim( $string, '/\\' ) . '/'; +} + +/** + * Convert Windows EOLs to *nix. + * + * @param string $str String to convert. + * @return string String with carriage return / newline pairs reduced to newlines. + */ +function normalize_eols( $str ) { + return str_replace( "\r\n", "\n", $str ); +} + +/** + * Get the system's temp directory. Warns user if it isn't writable. + * + * @access public + * @category System + * + * @return string + */ +function get_temp_dir() { + static $temp = ''; + + if ( $temp ) { + return $temp; + } + + // `sys_get_temp_dir()` introduced PHP 5.2.1. Will always return something. + $temp = trailingslashit( sys_get_temp_dir() ); + + if ( ! is_writable( $temp ) ) { + \EE::warning( "Temp directory isn't writable: {$temp}" ); + } + + return $temp; +} + +/** + * Parse a SSH url for its host, port, and path. + * + * Similar to parse_url(), but adds support for defined SSH aliases. + * + * ``` + * host OR host/path/to/wordpress OR host:port/path/to/wordpress + * ``` + * + * @access public + * + * @return mixed + */ +function parse_ssh_url( $url, $component = -1 ) { + preg_match( '#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches ); + $bits = array(); + foreach ( array( + 2 => 'scheme', + 4 => 'user', + 5 => 'host', + 7 => 'port', + 8 => 'path', + ) as $i => $key ) { + if ( ! empty( $matches[ $i ] ) ) { + $bits[ $key ] = $matches[ $i ]; + } + } + + // Find the hostname from `vagrant ssh-config` automatically. + if ( preg_match( '/^vagrant:?/', $url ) ) { + if ( 'vagrant' === $bits['host'] && empty( $bits['scheme'] ) ) { + $ssh_config = shell_exec( 'vagrant ssh-config 2>/dev/null' ); + if ( preg_match( '/Host\s(.+)/', $ssh_config, $matches ) ) { + $bits['scheme'] = 'vagrant'; + $bits['host'] = $matches[1]; + } + } + } + + switch ( $component ) { + case PHP_URL_SCHEME: + return isset( $bits['scheme'] ) ? $bits['scheme'] : null; + case PHP_URL_USER: + return isset( $bits['user'] ) ? $bits['user'] : null; + case PHP_URL_HOST: + return isset( $bits['host'] ) ? $bits['host'] : null; + case PHP_URL_PATH: + return isset( $bits['path'] ) ? $bits['path'] : null; + case PHP_URL_PORT: + return isset( $bits['port'] ) ? $bits['port'] : null; + default: + return $bits; + } +} + +/** + * Report the results of the same operation against multiple resources. + * + * @access public + * @category Input + * + * @param string $noun Resource being affected (e.g. plugin) + * @param string $verb Type of action happening to the noun (e.g. activate) + * @param integer $total Total number of resource being affected. + * @param integer $successes Number of successful operations. + * @param integer $failures Number of failures. + * @param null|integer $skips Optional. Number of skipped operations. Default null (don't show skips). + */ +function report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips = null ) { + $plural_noun = $noun . 's'; + $past_tense_verb = past_tense_verb( $verb ); + $past_tense_verb_upper = ucfirst( $past_tense_verb ); + if ( $failures ) { + $failed_skipped_message = null === $skips ? '' : " ({$failures} failed" . ( $skips ? ", {$skips} skipped" : '' ) . ')'; + if ( $successes ) { + EE::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}{$failed_skipped_message}." ); + } else { + EE::error( "No {$plural_noun} {$past_tense_verb}{$failed_skipped_message}." ); + } + } else { + $skipped_message = $skips ? " ({$skips} skipped)" : ''; + if ( $successes || $skips ) { + EE::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}{$skipped_message}." ); + } else { + $message = $total > 1 ? ucfirst( $plural_noun ) : ucfirst( $noun ); + EE::success( "{$message} already {$past_tense_verb}." ); + } + } +} + +/** + * Parse a string of command line arguments into an $argv-esqe variable. + * + * @access public + * @category Input + * + * @param string $arguments + * @return array + */ +function parse_str_to_argv( $arguments ) { + preg_match_all( '/(?<=^|\s)([\'"]?)(.+?)(? 'create', + 'check' => 'check-update', + 'capability' => 'cap', + 'clear' => 'flush', + 'decrement' => 'decr', + 'del' => 'delete', + 'directory' => 'dir', + 'exec' => 'eval', + 'exec-file' => 'eval-file', + 'increment' => 'incr', + 'language' => 'locale', + 'lang' => 'locale', + 'new' => 'create', + 'number' => 'count', + 'remove' => 'delete', + 'regen' => 'regenerate', + 'rep' => 'replace', + 'repl' => 'replace', + 'trash' => 'delete', + 'v' => 'version', + ); + + if ( array_key_exists( $target, $suggestion_map ) && in_array( $suggestion_map[ $target ], $options, true ) ) { + return $suggestion_map[ $target ]; + } + + if ( empty( $options ) ) { + return ''; + } + foreach ( $options as $option ) { + $distance = levenshtein( $option, $target ); + $levenshtein[ $option ] = $distance; + } + + // Sort known command strings by distance to user entry. + asort( $levenshtein ); + + // Fetch the closest command string. + reset( $levenshtein ); + $suggestion = key( $levenshtein ); + + // Only return a suggestion if below a given threshold. + return $levenshtein[ $suggestion ] <= $threshold && $suggestion !== $target + ? (string) $suggestion + : ''; +} + +/** + * Get a Phar-safe version of a path. + * + * For paths inside a Phar, this strips the outer filesystem's location to + * reduce the path to what it needs to be within the Phar archive. + * + * Use the __FILE__ or __DIR__ constants as a starting point. + * + * @param string $path An absolute path that might be within a Phar. + * + * @return string A Phar-safe version of the path. + */ +function phar_safe_path( $path ) { + + if ( ! inside_phar() ) { + return $path; + } + + return str_replace( + PHAR_STREAM_PREFIX . EE_PHAR_PATH . '/', + PHAR_STREAM_PREFIX, + $path + ); +} + +/** + * Check whether a given Command object is part of the bundled set of + * commands. + * + * This function accepts both a fully qualified class name as a string as + * well as an object that extends `EE\Dispatcher\CompositeCommand`. + * + * @param \EE\Dispatcher\CompositeCommand|string $command + * + * @return bool + */ +function is_bundled_command( $command ) { + static $classes; + + if ( null === $classes ) { + $classes = array(); + $class_map = EE_VENDOR_DIR . '/composer/autoload_commands_classmap.php'; + if ( file_exists( EE_VENDOR_DIR . '/composer/' ) ) { + $classes = include $class_map; + } + } + + if ( is_object( $command ) ) { + $command = get_class( $command ); + } + + return is_string( $command ) + ? array_key_exists( $command, $classes ) + : false; +} + +/** + * Maybe prefix command string with "/usr/bin/env". + * Removes (if there) if Windows, adds (if not there) if not. + * + * @param string $command + * + * @return string + */ +function force_env_on_nix_systems( $command ) { + $env_prefix = '/usr/bin/env '; + $env_prefix_len = strlen( $env_prefix ); + if ( is_windows() ) { + if ( 0 === strncmp( $command, $env_prefix, $env_prefix_len ) ) { + $command = substr( $command, $env_prefix_len ); + } + } else { + if ( 0 !== strncmp( $command, $env_prefix, $env_prefix_len ) ) { + $command = $env_prefix . $command; + } + } + return $command; +} + +/** + * Check that `proc_open()` and `proc_close()` haven't been disabled. + * + * @param string $context Optional. If set will appear in error message. Default null. + * @param bool $return Optional. If set will return false rather than error out. Default false. + * + * @return bool + */ +function check_proc_available( $context = null, $return = false ) { + if ( ! function_exists( 'proc_open' ) || ! function_exists( 'proc_close' ) ) { + if ( $return ) { + return false; + } + $msg = 'The PHP functions `proc_open()` and/or `proc_close()` are disabled. Please check your PHP ini directive `disable_functions` or suhosin settings.'; + if ( $context ) { + EE::error( sprintf( "Cannot do '%s': %s", $context, $msg ) ); + } else { + EE::error( $msg ); + } + } + return true; +} + +/** + * Returns past tense of verb, with limited accuracy. Only regular verbs catered for, apart from "reset". + * + * @param string $verb Verb to return past tense of. + * + * @return string + */ +function past_tense_verb( $verb ) { + static $irregular = array( + 'reset' => 'reset', + ); + if ( isset( $irregular[ $verb ] ) ) { + return $irregular[ $verb ]; + } + $last = substr( $verb, -1 ); + if ( 'e' === $last ) { + $verb = substr( $verb, 0, -1 ); + } elseif ( 'y' === $last && ! preg_match( '/[aeiou]y$/', $verb ) ) { + $verb = substr( $verb, 0, -1 ) . 'i'; + } elseif ( preg_match( '/^[^aeiou]*[aeiou][^aeiouhwxy]$/', $verb ) ) { + // Rule of thumb that most (all?) one-voweled regular verbs ending in vowel + consonant (excluding "h", "w", "x", "y") double their final consonant - misses many cases (eg "submit"). + $verb .= $last; + } + return $verb . 'ed'; +} + +/** + * Get the path to the PHP binary used when executing EE. + * + * Environment values permit specific binaries to be indicated. + * + * @access public + * @category System + * + * @return string + */ +function get_php_binary() { + if ( $ee_php_used = getenv( 'EE_PHP_USED' ) ) { + return $ee_php_used; + } + + if ( $ee_php = getenv( 'EE_PHP' ) ) { + return $ee_php; + } + + // Available since PHP 5.4. + if ( defined( 'PHP_BINARY' ) ) { + return PHP_BINARY; + } + + // @codingStandardsIgnoreLine + if ( @is_executable( PHP_BINDIR . '/php' ) ) { + return PHP_BINDIR . '/php'; + } + + // @codingStandardsIgnoreLine + if ( is_windows() && @is_executable( PHP_BINDIR . '/php.exe' ) ) { + return PHP_BINDIR . '/php.exe'; + } + + return 'php'; +} + +/** + * Windows compatible `proc_open()`. + * Works around bug in PHP, and also deals with *nix-like `ENV_VAR=blah cmd` environment variable prefixes. + * + * @access public + * + * @param string $command Command to execute. + * @param array $descriptorspec Indexed array of descriptor numbers and their values. + * @param array &$pipes Indexed array of file pointers that correspond to PHP's end of any pipes that are created. + * @param string $cwd Initial working directory for the command. + * @param array $env Array of environment variables. + * @param array $other_options Array of additional options (Windows only). + * + * @return string Command stripped of any environment variable settings. + */ +function proc_open_compat( $cmd, $descriptorspec, &$pipes, $cwd = null, $env = null, $other_options = null ) { + if ( is_windows() ) { + // Need to encompass the whole command in double quotes - PHP bug https://bugs.php.net/bug.php?id=49139 + $cmd = '"' . _proc_open_compat_win_env( $cmd, $env ) . '"'; + } + return proc_open( $cmd, $descriptorspec, $pipes, $cwd, $env, $other_options ); +} + +/** + * For use by `proc_open_compat()` only. Separated out for ease of testing. Windows only. + * Turns *nix-like `ENV_VAR=blah command` environment variable prefixes into stripped `cmd` with prefixed environment variables added to passed in environment array. + * + * @access private + * + * @param string $command Command to execute. + * @param array &$env Array of existing environment variables. Will be modified if any settings in command. + * + * @return string Command stripped of any environment variable settings. + */ +function _proc_open_compat_win_env( $cmd, &$env ) { + if ( false !== strpos( $cmd, '=' ) ) { + while ( preg_match( '/^([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|[^ ]*) /', $cmd, $matches ) ) { + $cmd = substr( $cmd, strlen( $matches[0] ) ); + if ( null === $env ) { + $env = array(); + } + $env[ $matches[1] ] = isset( $matches[2][0] ) && '"' === $matches[2][0] ? substr( $matches[2], 1, -1 ) : $matches[2]; + } + } + return $cmd; +} + +/** + * Check whether a given string is a valid JSON representation. + * + * @param string $argument String to evaluate. + * @param bool $ignore_scalars Optional. Whether to ignore scalar values. + * Defaults to true. + * + * @return bool Whether the provided string is a valid JSON representation. + */ +function is_json( $argument, $ignore_scalars = true ) { + if ( ! is_string( $argument ) || '' === $argument ) { + return false; + } + + if ( $ignore_scalars && ! in_array( $argument[0], array( '{', '[' ), true ) ) { + return false; + } + + json_decode( $argument, $assoc = true ); + + return json_last_error() === JSON_ERROR_NONE; +} + +/** + * Parse known shell arrays included in the $assoc_args array. + * + * @param array $assoc_args Associative array of arguments. + * @param array $array_arguments Array of argument keys that should receive an + * array through the shell. + * + * @return array + */ +function parse_shell_arrays( $assoc_args, $array_arguments ) { + if ( empty( $assoc_args ) || empty( $array_arguments ) ) { + return $assoc_args; + } + + foreach ( $array_arguments as $key ) { + if ( array_key_exists( $key, $assoc_args ) && is_json( $assoc_args[ $key ] ) ) { + $assoc_args[ $key ] = json_decode( $assoc_args[ $key ], $assoc = true ); + } + } + + return $assoc_args; +} + +/** + * Remove trailing slash from a string. + * + * @param string $str Input string. + * + * @return string String without trailing slash. + */ +function remove_trailing_slash( $str ) { + + return rtrim( $str, '/' ); +} + +/** + * Function to recursively copy directory. + * + * @param string $source Source directory. + * @param string $dest Destination directory. + * + * @return bool Success. + */ +function copy_recursive( $source, $dest ) { + + if ( ! is_dir( $dest ) ) { + if ( ! @mkdir( $dest, 0755 ) ) { + return false; + } + } + + foreach ( + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $source, \RecursiveDirectoryIterator::SKIP_DOTS ), + \RecursiveIteratorIterator::SELF_FIRST + ) as $item + ) { + if ( $item->isDir() ) { + if ( ! file_exists( $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName() ) ) { + mkdir( $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName() ); + } + } else { + copy( $item, $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName() ); + } + } + + return true; +} + +/** + * Delete directory. + * + * @param string $dir path to directory. + */ +function delete_dir( $dir ) { + $it = new \RecursiveDirectoryIterator( $dir, \RecursiveDirectoryIterator::SKIP_DOTS ); + $files = new \RecursiveIteratorIterator( + $it, + \RecursiveIteratorIterator::CHILD_FIRST + ); + foreach ( $files as $file ) { + if ( $file->isDir() ) { + @rmdir( $file->getRealPath() ); + } else { + @unlink( $file->getRealPath() ); + } + } + if ( @rmdir( $dir ) ) { + return true; + } + + return false; +} + +/** + * Function to generate random password. + * + * @param int $length Length of random password required. + * + * @return string Random Password of specified length. + */ +function random_password( $length = 18 ) { + $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; + $pass = array(); + $alphaLength = strlen( $alphabet ) - 1; + for ( $i = 0; $i < $length; $i ++ ) { + $n = rand( 0, $alphaLength ); + $pass[] = $alphabet[ $n ]; + } + + return implode( $pass ); +} + +/** + * Log data with deliminators for separate view + * + * @param String $log_data Data to log + * + * @TODO: can add withName parameter for future logging. + */ +function delem_log( $log_data ) { + EE::get_file_logger()->info( "======================== $log_data ========================" ); +} + +/** + * Function that takes care of executing the command as well as debugging it to terminal as well as file. + * + * @param string $command The command to be executed via exec(); + * + * @return bool True if executed successfully. False if failed. + */ +function default_exec( $command ) { + exec( $command, $out, $return_code ); + EE::debug( 'COMMAND: ' . $command ); + EE::debug( 'STDOUT: ' . implode( $out ) ); + EE::debug( 'RETURN CODE: ' . $return_code ); + if ( ! $return_code ) { + return true; + } + return false; +} + +/** + * Function that takes care of executing the command as well as debugging it to terminal as well as file. + * + * @param string $command The command to be executed via shell_exec(); + */ +function default_shell_exec( $command ) { + EE::debug( 'COMMAND: ' . $command ); + EE::debug( 'STDOUT: ' . shell_exec( $command ) ); +} + +/** + * Function to return the type from arguments. + * + * @param array $assoc_args User input arguments. + * @param array $arg_types Types to check with. + * @param mixed $default Default in case of no match + * + * @return string Type of site parsed from argument given from user. + */ +function get_type( $assoc_args, $arg_types, $default = false ) { + $type = ''; + $cnt = 0; + foreach ( $arg_types as $arg_type ) { + if ( get_flag_value( $assoc_args, $arg_type ) ) { + $cnt ++; + $type = $arg_type; + } + } + if ( $cnt == 1 ) { + return $type; + } elseif ( $cnt == 0 ) { + return $default; + } else { + return false; + } +} + +/** + * Render a collection of items as an ASCII table. + * + * Given a collection of items with a consistent data structure: + * + * ``` + * $items = array( + * array( 'key1' => 'value1'), + * array( 'key2' => 'value2'), + * ); + * ``` + * + * Render `$items` as an ASCII table: + * + * ``` + * EE\Utils\format_table( $items ); + * + * # +------+--------+ + * # | key1 | value1 | + * # +------+--------+ + * # | key2 | value2 | + * # +------+--------+ + * ``` + * + * @param array $items An array of items to output. + * @param bool $log_data To log table in file or not. + * + */ +function format_table( $items, $log_in_file = false ) { + $item_table = new \cli\Table(); + $item_table->setRows( $items ); + $item_table->setRenderer( new \cli\table\Ascii() ); + $lines = array_slice( $item_table->getDisplayLines(), 3 ); + array_pop( $lines ); + $delem = $item_table->getDisplayLines()[0]; + if ( $log_in_file ) { + foreach ( $lines as $line ) { + \EE::log( $delem ); + \EE::log( $line ); + } + } else { + foreach ( $lines as $line ) { + \EE::line( $delem ); + \EE::line( $line ); + } + } + \EE::log( $delem ); +} + +/** + * Function to flatten a multi-dimensional array. + * + * @param array $array Mulit-dimensional input array. + * + * @return array Resultant flattened array. + */ +function array_flatten( array $array ) { + $return = array(); + array_walk_recursive( + $array, function ( $a ) use ( &$return ) { + $return[] = $a; + } + ); + + return $return; +} + +/** + * Gets name of callable in string. Helpful while displaying it in error messages + * + * @param callable $callable Callable object + * + * @return string + */ +function get_callable_name( callable $callable ) { + if ( is_string( $callable ) ) { + return trim( $callable ); + } elseif ( is_array( $callable ) ) { + if ( is_object( $callable[0] ) ) { + return sprintf( '%s::%s', get_class( $callable[0] ), trim( $callable[1] ) ); + } else { + return sprintf( '%s::%s', trim( $callable[0] ), trim( $callable[1] ) ); + } + } elseif ( $callable instanceof \Closure ) { + return 'closure'; + } else { + return 'unknown'; + } +} + +/** + * Function to get the docker image versions stored in img-versions.json file. + * + * @return array Docker image versions. + */ +function get_image_versions() { + + $img_version_file = file_get_contents( EE_ROOT . '/img-versions.json' ); + if ( empty( $img_version_file ) ) { + EE::error( 'Image version file is empty. Can\'t proceed further.' ); + } + $img_versions = json_decode( $img_version_file, true ); + $json_error = json_last_error(); + if ( $json_error != JSON_ERROR_NONE ) { + EE::debug( 'Json last error: ' . $json_error ); + EE::error( 'Error decoding image version file.' ); + } + + return $img_versions; +} + +/** + * Function to get httpcode or port occupancy info. + * + * @param string $url url to get info about. + * @param int $port The port to check. + * @param bool $port_info Return port info or httpcode. + * @param mixed $auth Send http auth with passed value if not false. + * @param bool $resolve_localhost Wether to reolve curl request to localhost or not. + * + * @return bool|int port occupied or httpcode. + */ +function get_curl_info( $url, $port = 80, $port_info = false, $auth = false, $resolve_localhost = false ) { + + $ch = curl_init( $url ); + curl_setopt( $ch, CURLOPT_HEADER, true ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); + curl_setopt( $ch, CURLOPT_NOBODY, true ); + curl_setopt( $ch, CURLOPT_TIMEOUT, 10 ); + curl_setopt( $ch, CURLOPT_PORT, $port ); + if ( $resolve_localhost ) { + curl_setopt( $ch, CURLOPT_RESOLVE, [ $url . ':' . $port . ':' . LOCALHOST_IP ] ); + } + if ( $auth ) { + curl_setopt( $ch, CURLOPT_USERPWD, $auth ); + } + curl_exec( $ch ); + if ( $port_info ) { + return empty( curl_getinfo( $ch, CURLINFO_PRIMARY_IP ) ); + } + + return curl_getinfo( $ch, CURLINFO_HTTP_CODE ); +} + +/** + * Function to get config value for a given key. + * + * @param string $key Key to search in config file. + * @param string|null $default Default value of the given key. + * + * @return string|null value of the asked key. + */ +function get_config_value( $key, $default = null ) { + + $config_file_path = getenv( 'EE_CONFIG_PATH' ) ? getenv( 'EE_CONFIG_PATH' ) : EE_ROOT_DIR . '/config/config.yml'; + $existing_config = Spyc::YAMLLoad( $config_file_path ); + + return empty( $existing_config[ $key ] ) ? $default : $existing_config[ $key ]; +} + +/** + * Function to download file to a path. + * + * @param string $path Path to download the file on. + * @param string $download_url Url to download the file from. + */ +function download( $path, $download_url ) { + + $headers = array(); + $options = array( + 'timeout' => 1200, // 20 minutes ought to be enough for everybody. + 'filename' => $path, + ); + http_request( 'GET', $download_url, null, $headers, $options ); +} + +/** + * Extract zip files. + * + * @param string $zip_file Path to the zip file. + * @param string $path_to_extract Path where zip needs to be extracted to. + * + * @return bool Success of extraction. + */ +function extract_zip( $zip_file, $path_to_extract ) { + + $zip = new \ZipArchive; + $res = $zip->open( $zip_file ); + if ( true === $res ) { + $zip->extractTo( $path_to_extract ); + $zip->close(); + + return true; + } + + return false; +} + +/** + * Random name generator. + * + * @return string + */ +function random_name_generator() { + + $prefix = 'wp-user-'; + + // Generate a random suffix of 4 to 6 characters with a number at the 2nd or 3rd position. + $suffixLength = random_int( 4, 6 ); + $suffix = generate_safe_suffix( $suffixLength ); + + // Combine prefix and suffix to form the username + return $prefix . $suffix; +} + +/** + * Generate a safe random suffix with a number at the 2nd or 3rd position. + * + * @param int $length Length of the desired suffix. + * + * @return string + */ +function generate_safe_suffix( $length ) { + + $alphanumeric = 'abcdefghijklmnopqrstuvwxyz0123456789'; // Allowed alphanumeric characters + $numbers = '0123456789'; // Allowed numeric characters + + $randomSuffix = ''; + + // Randomly choose a position for the number (2nd or 3rd) + $numberPosition = random_int( 1, 2 ); + + for ( $i = 0; $i < $length; $i ++ ) { + if ( $i == $numberPosition ) { + $randomSuffix .= $numbers[ random_int( 0, strlen( $numbers ) - 1 ) ]; + } else { + $randomSuffix .= $alphanumeric[ random_int( 0, strlen( $alphanumeric ) - 1 ) ]; + } + } + + return $randomSuffix; +} + +/** + * @param array $assoc_args Arguments array. + * @param string $flag Flag to get the value. + * @param string $default_value default flag value if flag is passed without value. + * + * @return string + * @throws EE\ExitException + */ +function get_value_if_flag_isset( $assoc_args, $flag, $default_value = '' ) { + + $flag_value = get_flag_value( $assoc_args, $flag ); + $value = ''; + + if ( isset( $flag_value ) ) { + /** + * Set default flag value if flag is passed without value. + */ + $value = ( ! empty( $default_value ) && ( empty( $flag_value ) || true === $flag_value ) ) ? $default_value : $flag_value; + } + + return $value; +} + +/** + * Function to sanitize and remove illegal characters for folder and filename. + * + * @param string $input_name Input name to be sanitized. + * @param bool $strict Do strict replacement, i.e, remove all special characters except `-` and `_`. + * @param bool $remove_forward_slashes Wether to remove `/` or not from the input. + * + * @return string Sanitized name valid for file/folder creation. + */ +function sanitize_file_folder_name( $input_name, $strict = true, $remove_forward_slashes = false ) { + + $expression = $remove_forward_slashes ? '/[\"\*\/\:\<\>\?\'\|]+/' : '/[\"\*\:\<\>\?\'\|]+/'; + + // Remove Illegal Chars for folder and filename. + $output = preg_replace( $expression, '', $input_name ); + + if ( $strict ) { + // Remove all special characters except `-`, `_` and `/`. + $output = preg_replace( '/[^A-Za-z0-9\-_\/]/', '', $output ); + } + // Replace Spaces with dashes. + $output = str_replace( ' ', '-', $output ); + + // Replaces multiple hyphens with single one. + $output = preg_replace( '/-+/', '-', $output ); + + // Replaces multiple underscores with single one. + $output = preg_replace( '/_+/', '_', $output ); + + // Remove starting and ending hyphens as a starting hyphen in string might be considered as parameter in bash file/folder creation. + return trim( $output, '-' ); +} + diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 000000000..077ac221a --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,45 @@ + + + Coding Standards for EasyEngine + + + + + + + + + + + . + */ci/* + */features/* + */packages/* + */tests/* + */utils/* + */vendor/* + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 000000000..529dae0ef --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,11 @@ + + + + tests/ + tests/ + + + diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 24fb22183..000000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -cement>=2.4.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a273d468b..000000000 --- a/setup.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[nosetests] -verbosity=3 -debug=0 -detailed-errors=1 -with-coverage=1 -cover-package=ee -cover-erase=1 -cover-html=1 -cover-html-dir=coverage_report/ diff --git a/setup.py b/setup.py deleted file mode 100644 index bd088c13a..000000000 --- a/setup.py +++ /dev/null @@ -1,103 +0,0 @@ - -from setuptools import setup, find_packages -import sys -import os -import glob -import configparser -import re -import shutil - -conf = [] -templates = [] - -long_description = '''EasyEngine is the commandline tool to manage your - Websites based on WordPress and Nginx with easy to use - commands''' - -for name in glob.glob('config/plugins.d/*.conf'): - conf.insert(1, name) - -for name in glob.glob('ee/cli/templates/*.mustache'): - templates.insert(1, name) - -if not os.path.exists('/var/log/ee/'): - os.makedirs('/var/log/ee/') - -if not os.path.exists('/var/lib/ee/'): - os.makedirs('/var/lib/ee/') - -# EasyEngine git function -config = configparser.ConfigParser() -config.read(os.path.expanduser("~")+'/.gitconfig') -try: - ee_user = config['user']['name'] - ee_email = config['user']['email'] -except Exception as e: - print("EasyEngine (ee) required your name & email address to track" - " changes you made under the Git version control") - print("EasyEngine (ee) will be able to send you daily reports & alerts in " - "upcoming version") - print("EasyEngine (ee) will NEVER send your information across") - - ee_user = input("Enter your name: ") - while ee_user is "": - print("Name not Valid, Please enter again") - ee_user = input("Enter your name: ") - - ee_email = input("Enter your email: ") - - while not re.match(r"^[A-Za-z0-9\.\+_-]+@[A-Za-z0-9\._-]+\.[a-zA-Z]*$", - ee_email): - print("Invalid email address, please try again") - ee_email = input("Enter your email: ") - - os.system("git config --global user.name {0}".format(ee_user)) - os.system("git config --global user.email {0}".format(ee_email)) - -if not os.path.isfile('/root/.gitconfig'): - shutil.copy2(os.path.expanduser("~")+'/.gitconfig', '/root/.gitconfig') - -setup(name='ee', - version='3.8.1', - description=long_description, - long_description=long_description, - classifiers=[], - keywords='', - author='rtCamp Soultions Pvt. LTD', - author_email='ee@rtcamp.com', - url='http://rtcamp.com/easyengine', - license='MIT', - packages=find_packages(exclude=['ez_setup', 'examples', 'tests', - 'templates']), - include_package_data=True, - zip_safe=False, - test_suite='nose.collector', - install_requires=[ - # Required to build documentation - # "Sphinx >= 1.0", - # Required for testing - # "nose", - # "coverage", - # Required to function - 'cement == 2.4', - 'pystache', - 'python-apt', - 'pynginxconfig', - 'PyMySQL == 0.8.0', - 'psutil == 3.1.1', - 'sh', - 'SQLAlchemy', - ], - data_files=[('/etc/ee', ['config/ee.conf']), - ('/etc/ee/plugins.d', conf), - ('/usr/lib/ee/templates', templates), - ('/etc/bash_completion.d/', - ['config/bash_completion.d/ee_auto.rc']), - ('/usr/share/man/man8/', ['docs/ee.8'])], - setup_requires=[], - entry_points=""" - [console_scripts] - ee = ee.cli.main:main - """, - namespace_packages=[], - ) diff --git a/templates/README.md b/templates/README.md new file mode 100644 index 000000000..c6e996534 --- /dev/null +++ b/templates/README.md @@ -0,0 +1,4 @@ +Templates +========= + +The mustache templates for the EasyEngine core and mustache as well as default templated configurations required for different types of site are populated here. \ No newline at end of file diff --git a/templates/man-params.mustache b/templates/man-params.mustache new file mode 100644 index 000000000..6701b0f6d --- /dev/null +++ b/templates/man-params.mustache @@ -0,0 +1,19 @@ +{{#is_subcommand}} + + +{{/is_subcommand}} +{{#has_subcommands}} + + +{{/has_subcommands}} +## GLOBAL PARAMETERS + +{{#parameters}} + {{synopsis}} + {{desc}} + +{{/parameters}} +{{#root_command}} + Run 'ee help ' to get more information on a specific command. + +{{/root_command}} diff --git a/templates/man.mustache b/templates/man.mustache new file mode 100644 index 000000000..fbb2e9bd6 --- /dev/null +++ b/templates/man.mustache @@ -0,0 +1,28 @@ +## NAME + + {{name}} + +{{#shortdesc}} +## DESCRIPTION + + {{shortdesc}} + +{{/shortdesc}} +## SYNOPSIS + + {{synopsis}} + +{{#alias}} +## ALIAS + + {{alias}} + +{{/alias}} +{{#has-subcommands}} +## SUBCOMMANDS + +{{#subcommands}} + {{.}} +{{/subcommands}} + +{{/has-subcommands}} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 000000000..757e19f8f --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,20 @@ +] + * + * ## EXAMPLES + * + * $ wp foo command2 --path=/**a/**b/**c/** + */ + +final + protected + static + function + command2() { + } + + /** + * Command3 function + * + * ## OPTIONS + * + * [--path=] + * + * ## EXAMPLES + * + * $ wp foo command3 --path=/**a/**b/**c/** + function*/public function command3( $function ) {} + + function command4() {} +} + +/** + * Basic class + * + * ## EXAMPLES + * + * # Foo. + * $ wp foo --final abstract + class*/abstract class + CommandFactoryTests_Get_Doc_Comment_2_Command_Win + extends EE_Command + { + function command1() {} + } diff --git a/tests/data/commandfactory-doc_comment-class.php b/tests/data/commandfactory-doc_comment-class.php new file mode 100644 index 000000000..70ecbef9c --- /dev/null +++ b/tests/data/commandfactory-doc_comment-class.php @@ -0,0 +1,70 @@ +] + * + * ## EXAMPLES + * + * $ wp foo command2 --path=/**a/**b/**c/** + */ + +final + protected + static + function + command2() { + } + + /** + * Command3 function + * + * ## OPTIONS + * + * [--path=] + * + * ## EXAMPLES + * + * $ wp foo command3 --path=/**a/**b/**c/** + function*/public function command3( $function ) {} + + function command4() {} +} + +/** + * Basic class + * + * ## EXAMPLES + * + * # Foo. + * $ wp foo --final abstract + class*/abstract class + CommandFactoryTests_Get_Doc_Comment_2_Command + extends EE_Command + { + function command1() {} + } diff --git a/tests/data/commandfactory-doc_comment-function-win.php b/tests/data/commandfactory-doc_comment-function-win.php new file mode 100644 index 000000000..7fa1382e9 --- /dev/null +++ b/tests/data/commandfactory-doc_comment-function-win.php @@ -0,0 +1,19 @@ +=' ) ) { + + class_alias( 'PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase' ); + class_alias( 'PHPUnit\Framework\Exception', 'PHPUnit_Framework_Exception' ); + class_alias( 'PHPUnit\Framework\ExpectationFailedException', 'PHPUnit_Framework_ExpectationFailedException' ); + class_alias( 'PHPUnit\Framework\Error\Notice', 'PHPUnit_Framework_Error_Notice' ); + class_alias( 'PHPUnit\Framework\Error\Warning', 'PHPUnit_Framework_Error_Warning' ); + class_alias( 'PHPUnit\Framework\Test', 'PHPUnit_Framework_Test' ); + class_alias( 'PHPUnit\Framework\Warning', 'PHPUnit_Framework_Warning' ); + class_alias( 'PHPUnit\Framework\AssertionFailedError', 'PHPUnit_Framework_AssertionFailedError' ); + class_alias( 'PHPUnit\Framework\TestSuite', 'PHPUnit_Framework_TestSuite' ); + class_alias( 'PHPUnit\Framework\TestListener', 'PHPUnit_Framework_TestListener' ); + class_alias( 'PHPUnit\Util\GlobalState', 'PHPUnit_Util_GlobalState' ); + class_alias( 'PHPUnit\Util\Getopt', 'PHPUnit_Util_Getopt' ); + +} diff --git a/tests/test-arg-validation.php b/tests/test-arg-validation.php new file mode 100644 index 000000000..6056eef07 --- /dev/null +++ b/tests/test-arg-validation.php @@ -0,0 +1,64 @@ + []' ); + + $this->assertFalse( $validator->enough_positionals( array() ) ); + $this->assertTrue( $validator->enough_positionals( array( 1, 2 ) ) ); + $this->assertTrue( $validator->enough_positionals( array( 1, 2, 3, 4 ) ) ); + + $this->assertEquals( array( 4 ), $validator->unknown_positionals( array( 1, 2, 3, 4 ) ) ); + } + + function testRepeatingPositional() { + $validator = new SynopsisValidator( ' [...]' ); + + $this->assertFalse( $validator->enough_positionals( array() ) ); + $this->assertTrue( $validator->enough_positionals( array( 1 ) ) ); + $this->assertTrue( $validator->enough_positionals( array( 1, 2, 3 ) ) ); + + $this->assertEmpty( $validator->unknown_positionals( array( 1, 2, 3 ) ) ); + } + + function testUnknownAssocEmpty() { + $validator = new SynopsisValidator( '' ); + + $assoc_args = array( 'foo' => true, 'bar' => false ); + $this->assertEquals( array_keys( $assoc_args ), $validator->unknown_assoc( $assoc_args ) ); + } + + function testUnknownAssoc() { + $validator = new SynopsisValidator( '--type= [--brand=] [--flag]' ); + + $assoc_args = array( 'type' => 'analog', 'brand' => true, 'flag' => true ); + $this->assertEmpty( $validator->unknown_assoc( $assoc_args ) ); + + $assoc_args['another'] = true; + $this->assertContains( 'another', $validator->unknown_assoc( $assoc_args ) ); + } + + function testMissingAssoc() { + $validator = new SynopsisValidator( '--type= [--brand=] [--flag]' ); + + $assoc_args = array( 'brand' => true, 'flag' => true ); + list( $errors, $to_unset ) = $validator->validate_assoc( $assoc_args ); + + $this->assertCount( 1, $errors['fatal'] ); + $this->assertCount( 1, $errors['warning'] ); + } + + function testAssocWithOptionalValue() { + $validator = new SynopsisValidator( '[--network[=]]' ); + + $assoc_args = array( 'network' => true ); + list( $errors, $to_unset ) = $validator->validate_assoc( $assoc_args ); + + $this->assertCount( 0, $errors['fatal'] ); + $this->assertCount( 0, $errors['warning'] ); + } +} + diff --git a/tests/test-behat-tags.php b/tests/test-behat-tags.php new file mode 100644 index 000000000..19fd843c9 --- /dev/null +++ b/tests/test-behat-tags.php @@ -0,0 +1,133 @@ +temp_dir = Utils\get_temp_dir() . uniqid( 'ee-test-behat-tags-', true ); + mkdir( $this->temp_dir ); + mkdir( $this->temp_dir . '/features' ); + } + + function tearDown() { + + if ( $this->temp_dir && file_exists( $this->temp_dir ) ) { + foreach ( glob( $this->temp_dir . '/features/*' ) as $feature_file ) { + unlink( $feature_file ); + } + rmdir( $this->temp_dir . '/features' ); + rmdir( $this->temp_dir ); + } + + parent::tearDown(); + } + + /** + * @dataProvider data_behat_tags_wp_version_github_token + */ + function test_behat_tags_wp_version_github_token( $env, $expected ) { + $env_wp_version = getenv( 'WP_VERSION' ); + $env_github_token = getenv( 'GITHUB_TOKEN' ); + + putenv( 'WP_VERSION' ); + putenv( 'GITHUB_TOKEN' ); + + $behat_tags = dirname( __DIR__ ) . '/ci/behat-tags.php'; + + $contents = '@require-wp-4.6 @require-wp-4.8 @require-wp-4.9 @less-than-wp-4.6 @less-than-wp-4.8 @less-than-wp-4.9'; + file_put_contents( $this->temp_dir . '/features/wp_version.feature', $contents ); + + $output = exec( "cd {$this->temp_dir}; $env php $behat_tags" ); + $this->assertSame( '--tags=' . $expected . '&&~@broken', $output ); + + putenv( false === $env_wp_version ? 'WP_VERSION' : "WP_VERSION=$env_wp_version" ); + putenv( false === $env_github_token ? 'GITHUB_TOKEN' : "GITHUB_TOKEN=$env_github_token" ); + } + + function data_behat_tags_wp_version_github_token() { + return array( + array( 'WP_VERSION=4.5', '~@require-wp-4.6&&~@require-wp-4.8&&~@require-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=4.6', '~@require-wp-4.8&&~@require-wp-4.9&&~@less-than-wp-4.6&&~@github-api' ), + array( 'WP_VERSION=4.7', '~@require-wp-4.8&&~@require-wp-4.9&&~@less-than-wp-4.6&&~@github-api' ), + array( 'WP_VERSION=4.8', '~@require-wp-4.9&&~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@github-api' ), + array( 'WP_VERSION=4.9', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=5.0', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=latest', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=trunk', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=nightly', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( '', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'GITHUB_TOKEN=blah', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9' ), + ); + } + + function test_behat_tags_php_version() { + $env_github_token = getenv( 'GITHUB_TOKEN' ); + + putenv( 'GITHUB_TOKEN' ); + + $behat_tags = dirname( __DIR__ ) . '/ci/behat-tags.php'; + + $php_version = substr( PHP_VERSION, 0, 3 ); + $contents = $expected = ''; + + if ( '5.3' === $php_version ) { + $contents = '@require-php-5.2 @require-php-5.3 @require-php-5.4 @less-than-php-5.2 @less-than-php-5.3 @less-than-php-5.4'; + $expected = '~@require-php-5.4&&~@less-than-php-5.2&&~@less-than-php-5.3'; + } elseif ( '5.4' === $php_version ) { + $contents = '@require-php-5.3 @require-php-5.4 @require-php-5.5 @less-than-php-5.3 @less-than-php-5.4 @less-than-php-5.5'; + $expected = '~@require-php-5.5&&~@less-than-php-5.3&&~@less-than-php-5.4'; + } elseif ( '5.5' === $php_version ) { + $contents = '@require-php-5.4 @require-php-5.5 @require-php-5.6 @less-than-php-5.4 @less-than-php-5.5 @less-than-php-5.6'; + $expected = '~@require-php-5.6&&~@less-than-php-5.4&&~@less-than-php-5.5'; + } elseif ( '5.6' === $php_version ) { + $contents = '@require-php-5.5 @require-php-5.6 @require-php-7.0 @less-than-php-5.5 @less-than-php-5.6 @less-than-php-7.0'; + $expected = '~@require-php-7.0&&~@less-than-php-5.5&&~@less-than-php-5.6'; + } elseif ( '7.0' === $php_version ) { + $contents = '@require-php-5.6 @require-php-7.0 @require-php-7.1 @less-than-php-5.6 @less-than-php-7.0 @less-than-php-7.1'; + $expected = '~@require-php-7.1&&~@less-than-php-5.6&&~@less-than-php-7.0'; + } elseif ( '7.1' === $php_version ) { + $contents = '@require-php-7.0 @require-php-7.1 @require-php-7.2 @less-than-php-7.0 @less-than-php-7.1 @less-than-php-7.2'; + $expected = '~@require-php-7.2&&~@less-than-php-7.0&&~@less-than-php-7.1'; + } elseif ( '7.2' === $php_version ) { + $contents = '@require-php-7.1 @require-php-7.2 @require-php-7.3 @less-than-php-7.1 @less-than-php-7.2 @less-than-php-7.3'; + $expected = '~@require-php-7.3&&~@less-than-php-7.1&&~@less-than-php-7.2'; + } else { + $this->markTestSkipped( "No test for PHP_VERSION $php_version." ); + } + + file_put_contents( $this->temp_dir . '/features/php_version.feature', $contents ); + + $output = exec( "cd {$this->temp_dir}; php $behat_tags" ); + $this->assertSame( '--tags=' . $expected . '&&~@github-api&&~@broken', $output ); + + putenv( false === $env_github_token ? 'GITHUB_TOKEN' : "GITHUB_TOKEN=$env_github_token" ); + } + + function test_behat_tags_extension() { + $env_github_token = getenv( 'GITHUB_TOKEN' ); + + putenv( 'GITHUB_TOKEN' ); + + $behat_tags = dirname( __DIR__ ) . '/ci/behat-tags.php'; + + file_put_contents( $this->temp_dir . '/features/extension.feature', '@require-extension-imagick @require-extension-curl' ); + + $expecteds = array(); + if ( ! extension_loaded( 'imagick' ) ) { + $expecteds[] = '~@require-extension-imagick'; + } + if ( ! extension_loaded( 'curl' ) ) { + $expecteds[] = '~@require-extension-curl'; + } + $expected = '--tags=' . implode( '&&', array_merge( array( '~@github-api', '~@broken' ), $expecteds ) ); + $output = exec( "cd {$this->temp_dir}; php $behat_tags" ); + $this->assertSame( $expected, $output ); + + putenv( false === $env_github_token ? 'GITHUB_TOKEN' : "GITHUB_TOKEN=$env_github_token" ); + } +} diff --git a/tests/test-bundled-commands.php b/tests/test-bundled-commands.php new file mode 100644 index 000000000..500302186 --- /dev/null +++ b/tests/test-bundled-commands.php @@ -0,0 +1,43 @@ +assertEquals( $expected_result, $result ); + } + + public function dataProviderIsBundledCommands() { + return array( + // Bundled commands. + array( 'CLI_Command', true ), + array( new CLI_Command(), true ), + + // Commands not bundled. + array( 'Random_Unknown_Command', false ), + array( new Random_Unknown_Command(), false ), + + // Wrong data types. + array( array( 'CLI_Command' ), false ), + array( new stdClass(), false ), + array( 42, false ), + array( null, false ), + ); + } +} diff --git a/tests/test-commandfactory.php b/tests/test-commandfactory.php new file mode 100644 index 000000000..10ea679b9 --- /dev/null +++ b/tests/test-commandfactory.php @@ -0,0 +1,418 @@ +setAccessible( true ); + } + + $actual = $extract_last_doc_comment->invoke( null, $content ); + $this->assertSame( $expected, $actual ); + + // Restore. + putenv( false === $is_windows ? 'EE_TEST_IS_WINDOWS' : "EE_TEST_IS_WINDOWS=$is_windows" ); + } + + /** + * @dataProvider dataProviderExtractLastDocComment + */ + function testExtractLastDocCommentWin( $content, $expected ) { + // Save and set test env var. + $is_windows = getenv( 'EE_TEST_IS_WINDOWS' ); + putenv( 'EE_TEST_IS_WINDOWS=1' ); + + static $extract_last_doc_comment = null; + if ( null === $extract_last_doc_comment ) { + $extract_last_doc_comment = new \ReflectionMethod( 'EE\Dispatcher\CommandFactory', 'extract_last_doc_comment' ); + $extract_last_doc_comment->setAccessible( true ); + } + + $actual = $extract_last_doc_comment->invoke( null, $content ); + $this->assertSame( $expected, $actual ); + + // Restore. + putenv( false === $is_windows ? 'EE_TEST_IS_WINDOWS' : "EE_TEST_IS_WINDOWS=$is_windows" ); + } + + function dataProviderExtractLastDocComment() { + return array( + array( "", false ), + array( "*/", false ), + array( "/*/ ", false ), + array( "/**/", false ), + array( "/***/ */", false ), + array( "/***/", "/***/" ), + array( "\n /**\n \n \t\n */ \t\n \n ", "/**\n \n \t\n */" ), + array( "\r\n /**\r\n \r\n \t\r\n */ \t\r\n \r\n ", "/**\r\n \r\n \t\r\n */" ), + array( "/**/ /***/ /***/", "/***/" ), + array( "asdfasdf/** /** */", "/** /** */" ), + array( "*//** /** */", "/** /** */" ), + array( "/** *//** /** */", "/** /** */" ), + array( "*//** */ /** /** */", "/** /** */" ), + array( "*//** *//** /** /** */", "/** /** /** */" ), + + array( "/** */class qwer", "/** */" ), + array( "/**1*/class qwer{}/**2*/class asdf", "/**2*/" ), + array( "/** */class qwer {}\nclass asdf", false ), + array( "/** */class qwer {}\r\nclass asdf", false ), + + array( "/** */function qwer", "/** */" ), + array( "/** */function qwer( \$function ) {}", "/** */" ), + array( "/**1*/function qwer() {}/**2*/function asdf()", "/**2*/" ), + array( "/** */function qwer() {}\nfunction asdf()", false ), + array( "/** */function qwer() {}\r\nfunction asdf()", false ), + array( "/** */function qwer() {}function asdf()", false ), + array( "/** */function qwer() {};function asdf( \$function )", false ), + ); + } + + function testGetDocComment() { + // Save and set test env var. + $get_doc_comment = getenv( 'EE_TEST_GET_DOC_COMMENT' ); + $is_windows = getenv( 'EE_TEST_IS_WINDOWS' ); + + putenv( 'EE_TEST_GET_DOC_COMMENT=1' ); + putenv( 'EE_TEST_IS_WINDOWS=0' ); + + // Make private function accessible. + $get_doc_comment = new \ReflectionMethod( 'EE\Dispatcher\CommandFactory', 'get_doc_comment' ); + $get_doc_comment->setAccessible( true ); + + if ( ! class_exists( 'CommandFactoryTests_Get_Doc_Comment_1_Command', false ) ) { + require __DIR__ . '/data/commandfactory-doc_comment-class.php'; + } + if ( ! class_exists( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', false ) ) { + require __DIR__ . '/data/commandfactory-doc_comment-class-win.php'; + } + + // Class 1 + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 2 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command2' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 3 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command3' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 4 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command4' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Class 1 Windows + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 2 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command2' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 3 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command3' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 4 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command4' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Class 2 + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Class 2 Windows + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Functions + + require __DIR__ . '/data/commandfactory-doc_comment-function.php'; + + // Function 1 + + $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Function 2 + + $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_2' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Function 3 + + $reflection = new \ReflectionFunction( $commandfactorytests_get_doc_comment_func_3 ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Restore. + putenv( false === $get_doc_comment ? 'EE_TEST_GET_DOC_COMMENT' : "EE_TEST_GET_DOC_COMMENT=$get_doc_comment" ); + putenv( false === $is_windows ? 'EE_TEST_IS_WINDOWS' : "EE_TEST_IS_WINDOWS=$is_windows" ); + } + + function testGetDocCommentWin() { + // Save and set test env var. + $get_doc_comment = getenv( 'EE_TEST_GET_DOC_COMMENT' ); + $is_windows = getenv( 'EE_TEST_IS_WINDOWS' ); + + putenv( 'EE_TEST_GET_DOC_COMMENT=1' ); + putenv( 'EE_TEST_IS_WINDOWS=1' ); + + // Make private function accessible. + $get_doc_comment = new \ReflectionMethod( 'EE\Dispatcher\CommandFactory', 'get_doc_comment' ); + $get_doc_comment->setAccessible( true ); + + if ( ! class_exists( 'CommandFactoryTests_Get_Doc_Comment_1_Command', false ) ) { + require __DIR__ . '/data/commandfactory-doc_comment-class.php'; + } + if ( ! class_exists( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', false ) ) { + require __DIR__ . '/data/commandfactory-doc_comment-class-win.php'; + } + + // Class 1 + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 2 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command2' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 3 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command3' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 4 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command4' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Class 1 Windows + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 2 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command2' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 3 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command3' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 4 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command4' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Class 2 + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Class 2 Windows + + $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Class method 1 + + $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win', 'command1' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + $this->assertFalse( $actual ); + + // Functions + + require __DIR__ . '/data/commandfactory-doc_comment-function-win.php'; + + // Function 1 Windows + + $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_1_win' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Function 2 + + $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_2_win' ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Function 3 + + $reflection = new \ReflectionFunction( $commandfactorytests_get_doc_comment_func_3_win ); + $expected = $reflection->getDocComment(); + + $actual = $get_doc_comment->invoke( null, $reflection ); + $this->assertSame( $expected, $actual ); + + // Restore. + putenv( false === $get_doc_comment ? 'EE_TEST_GET_DOC_COMMENT' : "EE_TEST_GET_DOC_COMMENT=$get_doc_comment" ); + putenv( false === $is_windows ? 'EE_TEST_IS_WINDOWS' : "EE_TEST_IS_WINDOWS=$is_windows" ); + } +} diff --git a/tests/test-configurator.php b/tests/test-configurator.php new file mode 100644 index 000000000..cd20e36c5 --- /dev/null +++ b/tests/test-configurator.php @@ -0,0 +1,65 @@ +assertCount( 1, $args[0] ); + $this->assertCount( 2, $args[1] ); + + $this->assertEquals( 'foo', $args[0][0] ); + + $this->assertEquals( 'bar', $args[1][0][0] ); + $this->assertTrue( $args[1][0][1] ); + + $this->assertEquals( 'baz', $args[1][1][0] ); + $this->assertEquals( 'text', $args[1][1][1] ); + + } + + function testExtractAssocNoValue() { + $args = Configurator::extract_assoc( array( 'foo', '--bar=', '--baz=text' ) ); + + $this->assertCount( 1, $args[0] ); + $this->assertCount( 2, $args[1] ); + + $this->assertEquals( 'foo', $args[0][0] ); + + $this->assertEquals( 'bar', $args[1][0][0] ); + $this->assertEmpty( $args[1][0][1] ); + + $this->assertEquals( 'baz', $args[1][1][0] ); + $this->assertEquals( 'text', $args[1][1][1] ); + + } + + function testExtractAssocGlobalLocal() { + $args = Configurator::extract_assoc( array( '--url=foo.dev', '--path=wp', 'foo', '--bar=', '--baz=text', '--url=bar.dev' ) ); + + $this->assertCount( 1, $args[0] ); + $this->assertCount( 5, $args[1] ); + $this->assertCount( 2, $args[2] ); + $this->assertCount( 3, $args[3] ); + + $this->assertEquals( 'url', $args[2][0][0] ); + $this->assertEquals( 'foo.dev', $args[2][0][1] ); + $this->assertEquals( 'url', $args[3][2][0] ); + $this->assertEquals( 'bar.dev', $args[3][2][1] ); + } + + function testExtractAssocDoubleDashInValue() { + $args = Configurator::extract_assoc( array( '--test=text--text' ) ); + + $this->assertCount( 0, $args[0] ); + $this->assertCount( 1, $args[1] ); + + $this->assertEquals( 'test', $args[1][0][0] ); + $this->assertEquals( 'text--text', $args[1][0][1] ); + + } + + +} diff --git a/tests/test-doc-parser.php b/tests/test-doc-parser.php new file mode 100644 index 000000000..7ede9ff6c --- /dev/null +++ b/tests/test-doc-parser.php @@ -0,0 +1,200 @@ +assertEquals( '', $doc->get_shortdesc() ); + $this->assertEquals( '', $doc->get_longdesc() ); + $this->assertEquals( '', $doc->get_synopsis() ); + $this->assertEquals( '', $doc->get_tag('alias') ); + } + + function test_only_tags() { + $doc = new DocParser( <<assertEquals( '', $doc->get_shortdesc() ); + $this->assertEquals( '', $doc->get_longdesc() ); + $this->assertEquals( '', $doc->get_synopsis() ); + $this->assertEquals( '', $doc->get_tag('foo') ); + $this->assertEquals( 'rock-on', $doc->get_tag('alias') ); + $this->assertEquals( 'revoke-md5-passwords', $doc->get_tag('subcommand') ); + } + + function test_no_longdesc() { + $doc = new DocParser( <<assertEquals( 'Rock and roll!', $doc->get_shortdesc() ); + $this->assertEquals( '', $doc->get_longdesc() ); + $this->assertEquals( '', $doc->get_synopsis() ); + $this->assertEquals( 'rock-on', $doc->get_tag('alias') ); + } + + function test_complete() { + $doc = new DocParser( <<... + * : Start with one or more genres. + * + * --volume= + * : Sets the volume. + * + * --artist= + * : Limit to a specific artist. + * + * ## EXAMPLES + * + * wp rock-on --volume=11 + * + * @synopsis [--volume=] + * @alias rock-on + */ +EOB + ); + + $this->assertEquals( 'Rock and roll!', $doc->get_shortdesc() ); + $this->assertEquals( '[--volume=]', $doc->get_synopsis() ); + $this->assertEquals( 'Start with one or more genres.', $doc->get_arg_desc( 'genre' ) ); + $this->assertEquals( 'Sets the volume.', $doc->get_param_desc( 'volume' ) ); + $this->assertEquals( 'rock-on', $doc->get_tag('alias') ); + + $longdesc = <<... +: Start with one or more genres. + +--volume= +: Sets the volume. + +--artist= +: Limit to a specific artist. + +## EXAMPLES + +wp rock-on --volume=11 +EOB + ; + $this->assertEquals( $longdesc, $doc->get_longdesc() ); + } + + public function test_desc_parses_yaml() { + $longdesc = <<... +: Start with one or more genres. +--- +options: + - rock + - electronic +default: rock +--- + +--volume= +: Sets the volume. +--- +default: 10 +--- + +--artist= +: Limit to a specific artist. + +## EXAMPLES + +wp rock-on electronic --volume=11 + +EOB; + $doc = new DocParser( $longdesc ); + $this->assertEquals( 'Start with one or more genres.', $doc->get_arg_desc( 'genre' ) ); + $this->assertEquals( 'Sets the volume.', $doc->get_param_desc( 'volume' ) ); + $this->assertEquals( array( + 'options' => array( 'rock', 'electronic' ), + 'default' => 'rock', + ), $doc->get_arg_args( 'genre' ) ); + $this->assertEquals( array( + 'default' => 10, + ), $doc->get_param_args( 'volume' ) ); + $this->assertNull( $doc->get_param_args( 'artist' ) ); + } + + public function test_desc_doesnt_parse_far_params_yaml() { + $longdesc = << +: The name of the action or filter. + +[--format=] +: List callbacks as a table, JSON, CSV, or YAML. +--- +default: table +options: + - table + - json + - csv + - yaml +--- +EOB; + $doc = new DocParser( $longdesc ); + $this->assertEquals( array( + 'default' => 'table', + 'options' => array( 'table', 'json', 'csv', 'yaml' ), + ), $doc->get_param_args( 'format' ) ); + $this->assertNull( $doc->get_arg_args( 'hook' ) ); + } + + public function test_desc_doesnt_parse_far_args_yaml() { + $longdesc = << +: The name of the action or filter. + + +: List callbacks as a table, JSON, CSV, or YAML. +--- +default: table +options: + - table + - json + - csv + - yaml +--- +EOB; + $doc = new DocParser( $longdesc ); + $this->assertEquals( array( + 'default' => 'table', + 'options' => array( 'table', 'json', 'csv', 'yaml' ), + ), $doc->get_arg_args( 'format' ) ); + $this->assertNull( $doc->get_arg_args( 'hook' ) ); + } + +} + diff --git a/tests/test-ee.php b/tests/test-ee.php new file mode 100644 index 000000000..82d5da647 --- /dev/null +++ b/tests/test-ee.php @@ -0,0 +1,8 @@ +assertSame( EE\Utils\get_php_binary(), EE::get_php_binary() ); + } +} diff --git a/tests/test-extractor.php b/tests/test-extractor.php new file mode 100644 index 000000000..8d3ba5a3f --- /dev/null +++ b/tests/test-extractor.php @@ -0,0 +1,289 @@ +setAccessible( true ); + self::$prev_logger = $class_ee_logger->getValue(); + + self::$logger = new \EE\Loggers\Execution; + $logger_quiet = new \EE\Loggers\Quiet; + EE::set_logger( self::$logger ); + EE::set_file_logger( $logger_quiet ); + + // Remove any failed tests detritus. + $temp_dirs = Utils\get_temp_dir() . self::$copy_overwrite_files_prefix . '*'; + foreach ( glob( $temp_dirs ) as $temp_dir ) { + Extractor::rmdir( $temp_dir ); + } + } + + public function tearDown() { + // Restore logger. + EE::set_logger( self::$prev_logger ); + + parent::tearDown(); + } + + public function test_rmdir() { + list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); + + $this->assertTrue( is_dir( $wp_dir ) ); + Extractor::rmdir( $wp_dir ); + $this->assertFalse( file_exists( $wp_dir ) ); + + $this->assertTrue( is_dir( $temp_dir ) ); + Extractor::rmdir( $temp_dir ); + $this->assertFalse( file_exists( $temp_dir ) ); + } + + public function test_err_rmdir() { + $msg = ''; + try { + Extractor::rmdir( 'no-such-dir' ); + } catch ( \Exception $e ) { + $msg = $e->getMessage(); + } + $this->assertTrue( false !== strpos( $msg, 'no-such-dir' ) ); + $this->assertTrue( empty( self::$logger->stderr ) ); + } + + public function test_copy_overwrite_files() { + list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); + + $dest_dir = $temp_dir . '/dest'; + + Extractor::copy_overwrite_files( $wp_dir, $dest_dir ); + + $files = self::recursive_scandir( $dest_dir ); + + $this->assertSame( self::$expected_wp, $files ); + $this->assertTrue( empty( self::$logger->stderr ) ); + + // Clean up. + Extractor::rmdir( $temp_dir ); + } + + public function test_err_copy_overwrite_files() { + $msg = ''; + try { + Extractor::copy_overwrite_files( 'no-such-dir', 'dest-dir' ); + } catch ( \Exception $e ) { + $msg = $e->getMessage(); + } + $this->assertTrue( false !== strpos( $msg, 'no-such-dir' ) ); + $this->assertTrue( empty( self::$logger->stderr ) ); + } + + public function test_extract_tarball() { + if ( ! exec( 'tar --version' ) ) { + $this->markTestSkipped( 'tar not installed.' ); + } + + list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); + + $tarball = $temp_dir . '/test.tar.gz'; + $dest_dir = $temp_dir . '/dest'; + + // Create test tarball. + $output = array(); + $return_var = -1; + // Need --force-local for Windows to avoid "C:" being interpreted as being on remote machine, and redirect for Mac as outputs verbosely on STDERR. + $cmd = 'tar czvf %1$s' . ( Utils\is_windows() ? ' --force-local' : '' ) . ' --directory=%2$s/src wordpress 2>&1'; + exec( Utils\esc_cmd( $cmd, $tarball, $temp_dir ), $output, $return_var ); + $this->assertSame( 0, $return_var ); + $this->assertFalse( empty( $output ) ); + + // Normalize (Mac) output. + $output = array_map( function ( $v ) { + if ( 'a ' === substr( $v, 0, 2 ) ) { + $v = substr( $v, 2 ); + } + if ( '/' !== substr( $v, -1 ) && false === strpos( $v, '.' ) ) { + $v .= '/'; + } + return $v; + }, $output ); + sort( $output ); + + $this->assertSame( self::recursive_scandir( $src_dir ), $output ); + + // Test. + Extractor::extract( $tarball, $dest_dir ); + + $files = self::recursive_scandir( $dest_dir ); + $this->assertSame( self::$expected_wp, $files ); + $this->assertTrue( empty( self::$logger->stderr ) ); + + // Clean up. + Extractor::rmdir( $temp_dir ); + } + + public function test_err_extract_tarball() { + // Non-existent. + $msg = ''; + try { + Extractor::extract( 'no-such-tar.tar.gz', 'dest-dir' ); + } catch ( \Exception $e ) { + $msg = $e->getMessage(); + } + $this->assertTrue( false !== strpos( $msg, 'no-such-tar' ) ); + $this->assertTrue( 0 === strpos( self::$logger->stderr, 'Warning: PharData failed' ) ); + $this->assertTrue( false !== strpos( self::$logger->stderr, 'no-such-tar' ) ); + + self::$logger->stderr = self::$logger->stdout = ''; // Reset logger. + + // Zero-length. + $zero_tar = Utils\get_temp_dir() . 'zero-tar.tar.gz'; + touch( $zero_tar ); + $msg = ''; + try { + Extractor::extract( $zero_tar, 'dest-dir' ); + } catch ( \Exception $e ) { + $msg = $e->getMessage(); + } + unlink( $zero_tar ); + $this->assertTrue( false !== strpos( $msg, 'zero-tar' ) ); + $this->assertTrue( 0 === strpos( self::$logger->stderr, 'Warning: PharData failed' ) ); + $this->assertTrue( false !== strpos( self::$logger->stderr, 'zero-tar' ) ); + } + + public function test_extract_zip() { + if ( ! class_exists( 'ZipArchive' ) ) { + $this->markTestSkipped( 'ZipArchive not installed.' ); + } + + list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); + + $zipfile = $temp_dir . '/test.zip'; + $dest_dir = $temp_dir . '/dest'; + + // Create test zip. + $zip = new ZipArchive; + $result = $zip->open( $zipfile, ZipArchive::CREATE ); + $this->assertTrue( $result ); + $files = self::recursive_scandir( $src_dir ); + foreach ( $files as $file ) { + if ( 0 === substr_compare( $file, '/', -1 ) ) { + $result = $zip->addEmptyDir( $file ); + } else { + $result = $zip->addFile( $src_dir . '/' . $file, $file ); + } + $this->assertTrue( $result ); + } + $result = $zip->close(); + $this->assertTrue( $result ); + + // Test. + Extractor::extract( $zipfile, $dest_dir ); + + $files = self::recursive_scandir( $dest_dir ); + $this->assertSame( self::$expected_wp, $files ); + $this->assertTrue( empty( self::$logger->stderr ) ); + + // Clean up. + Extractor::rmdir( $temp_dir ); + } + + public function test_err_extract_zip() { + if ( ! class_exists( 'ZipArchive' ) ) { + $this->markTestSkipped( 'ZipArchive not installed.' ); + } + + // Non-existent. + $msg = ''; + try { + Extractor::extract( 'no-such-zip.zip', 'dest-dir' ); + } catch ( \Exception $e ) { + $msg = $e->getMessage(); + } + $this->assertTrue( false !== strpos( $msg, 'no-such-zip' ) ); + $this->assertTrue( empty( self::$logger->stderr ) ); + + self::$logger->stderr = self::$logger->stdout = ''; // Reset logger. + + // Zero-length. + $zero_zip = Utils\get_temp_dir() . 'zero-zip.zip'; + touch( $zero_zip ); + $msg = ''; + try { + Extractor::extract( $zero_zip, 'dest-dir' ); + } catch ( \Exception $e ) { + $msg = $e->getMessage(); + } + unlink( $zero_zip ); + $this->assertTrue( false !== strpos( $msg, 'zero-zip' ) ); + $this->assertTrue( empty( self::$logger->stderr ) ); + } + + public function test_err_extract() { + $msg = ''; + try { + Extractor::extract( 'not-supported.tar.xz', 'dest-dir' ); + } catch ( \Exception $e ) { + $msg = $e->getMessage(); + } + $this->assertSame( "Extraction only supported for '.zip' and '.tar.gz' file types.", $msg ); + $this->assertTrue( empty( self::$logger->stderr ) ); + } + + private function create_test_directory_structure() { + $temp_dir = Utils\get_temp_dir() . uniqid( self::$copy_overwrite_files_prefix, true ); + mkdir( $temp_dir ); + + $src_dir = $temp_dir . '/src'; + mkdir( $src_dir ); + + $wp_dir = $src_dir . '/wordpress'; + mkdir( $wp_dir ); + + foreach ( self::$expected_wp as $file ) { + if ( 0 === substr_compare( $file, '/', -1 ) ) { + mkdir( $wp_dir . '/' . $file ); + } else { + touch( $wp_dir . '/' . $file ); + } + } + + return array( $temp_dir, $src_dir, $wp_dir ); + } + + private function recursive_scandir( $dir, $prefix_dir = '' ) { + $ret = array(); + foreach ( array_diff( scandir( $dir ), array( '.', '..' ) ) as $file ) { + if ( is_dir( $dir . '/' . $file ) ) { + $ret[] = ( $prefix_dir ? ( $prefix_dir . '/'. $file ) : $file ) . '/'; + $ret = array_merge( $ret, self::recursive_scandir( $dir . '/' . $file, $prefix_dir ? ( $prefix_dir . '/' . $file ) : $file ) ); + } else { + $ret[] = $prefix_dir ? ( $prefix_dir . '/'. $file ) : $file; + } + } + return $ret; + } +} diff --git a/tests/test-file-cache.php b/tests/test-file-cache.php new file mode 100644 index 000000000..eec03c2cb --- /dev/null +++ b/tests/test-file-cache.php @@ -0,0 +1,84 @@ +assertSame( $cache_dir . '/', $cache->get_root() ); + unset( $cache ); + + $cache = new FileCache( $cache_dir . '/', $ttl, $max_size ); + $this->assertSame( $cache_dir . '/', $cache->get_root() ); + unset( $cache ); + + $cache = new FileCache( $cache_dir . '\\', $ttl, $max_size ); + $this->assertSame( $cache_dir . '/', $cache->get_root() ); + unset( $cache ); + + rmdir( $cache_dir ); + } + + public function test_ensure_dir_exists() { + $class_ee_logger = new ReflectionProperty( 'EE', 'logger' ); + $class_ee_logger->setAccessible( true ); + $prev_logger = $class_ee_logger->getValue(); + + $logger = new EE\Loggers\Execution; + EE::set_logger( $logger ); + + $max_size = 32; + $ttl = 60; + $cache_dir = Utils\get_temp_dir() . uniqid( 'ee-test-file-cache', true ); + + $cache = new FileCache( $cache_dir, $ttl, $max_size ); + $test_class = new ReflectionClass( $cache ); + $method = $test_class->getMethod( 'ensure_dir_exists' ); + $method->setAccessible( true ); + + // Cache directory should be created. + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test1' ) ); + $this->assertTrue( $result ); + $this->assertTrue( is_dir( $cache_dir . '/test1' ) ); + + // Try to create the same directory again. it should return true. + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test1' ) ); + $this->assertTrue( $result ); + + // `chmod()` doesn't work on Windows. + if ( ! Utils\is_windows() ) { + // It should be failed because permission denied. + $logger->stderr = ''; + chmod( $cache_dir . '/test1', 0000 ); + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test1/error' ) ); + $expected = "/^Warning: Failed to create directory '.+': mkdir\(\): Permission denied\.$/"; + $this->assertRegexp( $expected, $logger->stderr ); + } + + // It should be failed because file exists. + $logger->stderr = ''; + file_put_contents( $cache_dir . '/test2', '' ); + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test2' ) ); + $expected = "/^Warning: Failed to create directory '.+': mkdir\(\): File exists\.$/"; + $this->assertRegexp( $expected, $logger->stderr ); + + // Restore + chmod( $cache_dir . '/test1', 0755 ); + rmdir( $cache_dir . '/test1' ); + unlink( $cache_dir . '/test2' ); + rmdir( $cache_dir ); + $class_ee_logger->setValue( $prev_logger ); + } +} diff --git a/tests/test-help.php b/tests/test-help.php new file mode 100644 index 000000000..d9c0441b4 --- /dev/null +++ b/tests/test-help.php @@ -0,0 +1,117 @@ +getMethod( 'parse_reference_links' ); + $method->setAccessible( true ); + + $desc = 'This is a [reference link](https://wordpress.org/). It should be displayed very nice!'; + $result = $method->invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc = 'This is a [reference link](https://wordpress.org/) and [second link](http://ee.org/). It should be displayed very nice!'; + $result = $method->invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + } +} diff --git a/tests/test-logging.php b/tests/test-logging.php new file mode 100644 index 000000000..1889d7279 --- /dev/null +++ b/tests/test-logging.php @@ -0,0 +1,113 @@ + array ( + 'debug' => true + ) + ); + } + + protected function write( $handle, $str ) { + echo $str; + } +} + +class MockQuietLogger extends EE\Loggers\Quiet { + + protected function get_runner() { + return (object) array ( + 'config' => array ( + 'debug' => true + ) + ); + } +} + +class LoggingTests extends PHPUnit_Framework_TestCase { + + function testLogDebug() { + $message = 'This is a test message.'; + + $regularLogger = new MockRegularLogger( false ); + $this->expectOutputRegex( "/Debug: {$message} \(\d+\.*\d*s\)/" ); + $regularLogger->debug( $message ); + + $quietLogger = new MockQuietLogger(); + $this->expectOutputRegex( "/Debug: {$message} \(\d+\.*\d*s\)/" ); + $quietLogger->debug( $message ); + } + + function testLogEscaping() { + $logger = new MockRegularLogger( false ); + + $message = 'foo%20bar'; + + $this->expectOutputString( "Success: $message\n" ); + $logger->success( $message ); + } + + function testExecutionLogger() { + // Save Runner config. + $runner = EE::get_runner(); + $runner_config = new \ReflectionProperty( $runner, 'config' ); + $runner_config->setAccessible( true ); + + $prev_config = $runner_config->getValue( $runner ); + + // Set debug. + $runner_config->setValue( $runner, array( 'debug' => true ) ); + + $logger = new EE\Loggers\Execution; + + // Standard use. + + $logger->info( 'info' ); + $logger->info( 'info2' ); + $logger->success( 'success' ); + $logger->warning( 'warning' ); + $logger->error( 'error' ); + $logger->success( 'success2' ); + $logger->warning( 'warning2' ); + $logger->debug( 'debug', 'group' ); + $logger->error_multi_line( array( "line11", "line12", "line13" ) ); + $logger->error( 'error2' ); + $logger->error_multi_line( array( "line21" ) ); + $logger->debug( 'debug2', 'group2' ); + + $this->assertSame( "info\ninfo2\nSuccess: success\nSuccess: success2\n", $logger->stdout ); + $this->assertSame( 1, preg_match( '/^' + . 'Warning: warning\nError: error\n' + . 'Warning: warning2\nDebug \(group\): debug \([0-9.]+s\)\n' + . 'Error:\nline11\nline12\nline13\n---------\n\nError: error2\n' + . 'Error:\nline21\n---------\n\nDebug \(group2\): debug2 \([0-9.]+s\)$/', $logger->stderr ) ); + + $logger->stdout = $logger->stderr = ''; + + // With output buffering. + + $logger->ob_start(); + + echo "echo"; + $logger->info( 'info' ); + print "print\n"; + $logger->success( 'success' ); + echo "echo2\n"; + $logger->error( 'error' ); + echo "echo3\n"; + $logger->success( 'success2' ); + echo "echo4"; + + $logger->ob_end(); + + $this->assertSame( "echoinfo\nprint\nSuccess: success\necho2\necho3\nSuccess: success2\necho4", $logger->stdout ); + $this->assertSame( "Error: error\n", $logger->stderr ); + + $logger->stdout = $logger->stderr = ''; + + // Restore. + $runner_config->setValue( $runner, $prev_config ); + } +} diff --git a/tests/test-process.php b/tests/test-process.php new file mode 100644 index 000000000..e51b9782f --- /dev/null +++ b/tests/test-process.php @@ -0,0 +1,29 @@ +run(); + + $this->assertSame( $process_run->stdout, $expected_out ); + } + + function data_process_env() { + return array( + array( '', array(), array(), '' ), + array( 'ENV=blah', array(), array( 'ENV' ), 'blah' ), + array( 'ENV="blah blah"', array(), array( 'ENV' ), 'blah blah' ), + array( 'ENV_1="blah1 blah1" ENV_2="blah2" ENV_3=blah3', array( 'ENV' => 'in' ), array( 'ENV', 'ENV_1', 'ENV_2', 'ENV_3' ), 'inblah1 blah1blah2blah3' ), + array( 'ENV=blah', array( 'ENV_1' => 'in1', 'ENV_2' => 'in2' ), array( 'ENV_1', 'ENV_2', 'ENV' ), 'in1in2blah' ), + ); + } +} diff --git a/tests/test-synopsis.php b/tests/test-synopsis.php new file mode 100644 index 000000000..414f32885 --- /dev/null +++ b/tests/test-synopsis.php @@ -0,0 +1,185 @@ +assertEmpty( $r ); + } + + function testPositional() { + $r = SynopsisParser::parse( ' []' ); + + $this->assertCount( 2, $r ); + + $param = $r[0]; + $this->assertEquals( 'positional', $param['type'] ); + $this->assertFalse( $param['optional'] ); + + $param = $r[1]; + $this->assertEquals( 'positional', $param['type'] ); + $this->assertTrue( $param['optional'] ); + } + + function testFlag() { + $r = SynopsisParser::parse( '[--foo]' ); + + $this->assertCount( 1, $r ); + + $param = $r[0]; + $this->assertEquals( 'flag', $param['type'] ); + $this->assertTrue( $param['optional'] ); + + // flags can't be mandatory + $r = SynopsisParser::parse( '--foo' ); + + $this->assertCount( 1, $r ); + + $param = $r[0]; + $this->assertEquals( 'unknown', $param['type'] ); + } + + function testGeneric() { + $r = SynopsisParser::parse( '--= [--=] --[=] [--[=]]' ); + + $this->assertCount( 4, $r ); + + $param = $r[0]; + $this->assertEquals( 'generic', $param['type'] ); + $this->assertFalse( $param['optional'] ); + + $param = $r[1]; + $this->assertEquals( 'generic', $param['type'] ); + $this->assertTrue( $param['optional'] ); + + $param = $r[2]; + $this->assertEquals( 'unknown', $param['type'] ); + + $param = $r[3]; + $this->assertEquals( 'unknown', $param['type'] ); + } + + function testAssoc() { + $r = SynopsisParser::parse( '--foo= [--bar=] [--bar[=]]' ); + + $this->assertCount( 3, $r ); + + $param = $r[0]; + $this->assertEquals( 'assoc', $param['type'] ); + $this->assertFalse( $param['optional'] ); + + $param = $r[1]; + $this->assertEquals( 'assoc', $param['type'] ); + $this->assertTrue( $param['optional'] ); + + $param = $r[2]; + $this->assertEquals( 'assoc', $param['type'] ); + $this->assertTrue( $param['optional'] ); + $this->assertTrue( $param['value']['optional'] ); + } + + function testInvalidAssoc() { + $r = SynopsisParser::parse( '--bar[=] --bar=[] --count=100' ); + + $this->assertCount( 3, $r ); + + $this->assertEquals( 'unknown', $r[0]['type'] ); + $this->assertEquals( 'unknown', $r[1]['type'] ); + $this->assertEquals( 'unknown', $r[2]['type'] ); + } + + function testRepeating() { + $r = SynopsisParser::parse( '... [--=...]' ); + + $this->assertCount( 2, $r ); + + $param = $r[0]; + $this->assertEquals( 'positional', $param['type'] ); + $this->assertTrue( $param['repeating'] ); + + $param = $r[1]; + $this->assertEquals( 'generic', $param['type'] ); + $this->assertTrue( $param['repeating'] ); + } + + function testCombined() { + $r = SynopsisParser::parse( ' --assoc= --= [--flag]' ); + + $this->assertCount( 4, $r ); + + $this->assertEquals( 'positional', $r[0]['type'] ); + $this->assertEquals( 'assoc', $r[1]['type'] ); + $this->assertEquals( 'generic', $r[2]['type'] ); + $this->assertEquals( 'flag', $r[3]['type'] ); + } + + function testAllowedValueCharacters() { + $r = SynopsisParser::parse( '--capitals= --hyphen= --combined= --disallowed=' ); + + $this->assertCount( 4, $r ); + + $param = $r[0]; + $this->assertEquals( 'assoc', $param['type'] ); + $this->assertFalse( $param['optional'] ); + + $param = $r[1]; + $this->assertEquals( 'assoc', $param['type'] ); + $this->assertFalse( $param['optional'] ); + + $param = $r[2]; + $this->assertEquals( 'assoc', $param['type'] ); + $this->assertFalse( $param['optional'] ); + + $this->assertEquals( 'unknown', $r[3]['type'] ); + } + + function testRender() { + $a = array( + array( + 'name' => 'message', + 'type' => 'positional', + 'description' => 'A short message to display to the user.', + ), + array( + 'name' => 'secrets', + 'type' => 'positional', + 'description' => 'You may tell secrets, or you may not', + 'optional' => true, + 'repeating' => true, + ), + array( + 'name' => 'meal', + 'type' => 'assoc', + 'description' => 'A meal during the day or night.', + ), + array( + 'name' => 'snack', + 'type' => 'assoc', + 'description' => 'If you are hungry between meals, you should snack.', + 'optional' => true, + ) + ); + $this->assertEquals( ' [...] --meal= [--snack=]', SynopsisParser::render( $a ) ); + } + + function testParseThenRender() { + $o = ' --assoc= --= [--flag]'; + $a = SynopsisParser::parse( $o ); + $r = SynopsisParser::render( $a ); + $this->assertEquals( $o, $r ); + } + + function testParseThenRenderNumeric() { + $o = ' --a2ssoc= --= [--f3lag]'; + $a = SynopsisParser::parse( $o ); + $this->assertEquals( 'p1ositional', $a[0]['name'] ); + $this->assertEquals( 'a2ssoc', $a[1]['name'] ); + $this->assertEquals( 'f3lag', $a[3]['name'] ); + $r = SynopsisParser::render( $a ); + $this->assertEquals( $o, $r ); + } + +} diff --git a/tests/test-utils.php b/tests/test-utils.php new file mode 100644 index 000000000..c6b6c175a --- /dev/null +++ b/tests/test-utils.php @@ -0,0 +1,658 @@ +assertEquals( + Utils\increment_version( '1.2.3-pre', 'same' ), + '1.2.3-pre' + ); + + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', 'patch' ), + '1.2.4' + ); + + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', 'minor' ), + '1.3.0' + ); + + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', 'major' ), + '2.0.0' + ); + + // custom version string + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', '4.5.6-alpha1' ), + '4.5.6-alpha1' + ); + } + + public function testGetSemVer() { + $original_version = '0.19.1'; + $this->assertEmpty( Utils\get_named_sem_ver( '0.18.0', $original_version ) ); + $this->assertEmpty( Utils\get_named_sem_ver( '0.19.1', $original_version ) ); + $this->assertEquals( 'patch', Utils\get_named_sem_ver( '0.19.2', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '0.20.0', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '0.20.3', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '1.0.0', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '1.1.1', $original_version ) ); + } + + public function testGetSemVerWP() { + $original_version = '3.0'; + $this->assertEmpty( Utils\get_named_sem_ver( '2.8', $original_version ) ); + $this->assertEmpty( Utils\get_named_sem_ver( '2.9.1', $original_version ) ); + $this->assertEquals( 'patch', Utils\get_named_sem_ver( '3.0.1', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '3.1', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '3.1.1', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '4.0', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '4.1.1', $original_version ) ); + } + + public function testParseSSHUrl() { + $testcase = 'foo'; + $this->assertEquals( array( + 'host' => 'foo', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com'; + $this->assertEquals( array( + 'host' => 'foo.com', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com:2222'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'port' => 2222, + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( 2222, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com:2222/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'port' => 2222, + 'path' => '/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( 2222, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com~/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // No host + $testcase = '~/path/to/dir'; + $this->assertEquals( array(), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // host and path, no port, with scp notation + $testcase = 'foo.com:~/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com:2222~/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + 'port' => '2222' + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( '2222', Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // explicit scheme, user, host, path, no port + $testcase = 'ssh:bar@foo.com:~/path/to/dir'; + $this->assertEquals( array( + 'scheme' => 'ssh', + 'user' => 'bar', + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'ssh', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // container scheme + $testcase = 'docker:wordpress'; + $this->assertEquals( array( + 'scheme' => 'docker', + 'host' => 'wordpress', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'docker', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // container scheme with user, and host + $testcase = 'docker:bar@wordpress'; + $this->assertEquals( array( + 'scheme' => 'docker', + 'user' => 'bar', + 'host' => 'wordpress', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'docker', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // container scheme with user, host, and path + $testcase = 'docker-compose:bar@wordpress:~/path/to/dir'; + $this->assertEquals( array( + 'scheme' => 'docker-compose', + 'user' => 'bar', + 'host' => 'wordpress', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'docker-compose', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // vagrant scheme + $testcase = 'vagrant:default'; + $this->assertEquals( array( + 'scheme' => 'vagrant', + 'host' => 'default', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'vagrant', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'default', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // vagrant scheme + $testcase = 'vagrant:/var/www/html'; + $this->assertEquals( array( + 'host' => 'vagrant', + 'path' => '/var/www/html', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'vagrant', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '/var/www/html', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // unsupported scheme, should not match + $testcase = 'foo:bar'; + $this->assertEquals( array(), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + } + + public function testParseStrToArgv() { + $this->assertEquals( array(), Utils\parse_str_to_argv( '' ) ); + $this->assertEquals( array( + 'option', + 'get', + 'home', + ), Utils\parse_str_to_argv( 'option get home' ) ); + $this->assertEquals( array( + 'core', + 'download', + '--path=/var/www/', + ), Utils\parse_str_to_argv( 'core download --path=/var/www/' ) ); + $this->assertEquals( array( + 'eval', + 'echo wp_get_current_user()->user_login;', + ), Utils\parse_str_to_argv( 'eval "echo wp_get_current_user()->user_login;"' ) ); + } + + public function testAssocArgsToString() { + // Strip quotes for Windows compat. + $strip_quotes = function ( $str ) { + return str_replace( array( '"', "'" ), '', $str ); + }; + + $expected = " --url='foo.dev' --porcelain --apple='banana'"; + $actual = Utils\assoc_args_to_str( array( + 'url' => 'foo.dev', + 'porcelain' => true, + 'apple' => 'banana' + ) ); + $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); + + $expected = " --url='foo.dev' --require='file-a.php' --require='file-b.php' --porcelain --apple='banana'"; + $actual = Utils\assoc_args_to_str( array( + 'url' => 'foo.dev', + 'require' => array( + 'file-a.php', + 'file-b.php', + ), + 'porcelain' => true, + 'apple' => 'banana' + ) ); + $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); + } + + public function testForceEnvOnNixSystems() { + $env_is_windows = getenv( 'EE_TEST_IS_WINDOWS' ); + + putenv( 'EE_TEST_IS_WINDOWS=0' ); + $this->assertSame( '/usr/bin/env cmd', Utils\force_env_on_nix_systems( 'cmd' ) ); + $this->assertSame( '/usr/bin/env cmd', Utils\force_env_on_nix_systems( '/usr/bin/env cmd' ) ); + + putenv( 'EE_TEST_IS_WINDOWS=1' ); + $this->assertSame( 'cmd', Utils\force_env_on_nix_systems( 'cmd' ) ); + $this->assertSame( 'cmd', Utils\force_env_on_nix_systems( '/usr/bin/env cmd' ) ); + + putenv( false === $env_is_windows ? 'EE_TEST_IS_WINDOWS' : "EE_TEST_IS_WINDOWS=$env_is_windows" ); + } + + public function testGetHomeDir() { + + // save environments + $home = getenv( 'HOME' ); + $homedrive = getenv( 'HOMEDRIVE' ); + $homepath = getenv( 'HOMEPATH' ); + + putenv( 'HOME=/home/user' ); + $this->assertSame('/home/user', Utils\get_home_dir() ); + + putenv( 'HOME' ); + + putenv( 'HOMEDRIVE=D:' ); + putenv( 'HOMEPATH' ); + $this->assertSame( 'D:', Utils\get_home_dir() ); + + putenv( 'HOMEPATH=\\Windows\\User\\' ); + $this->assertSame( 'D:\\Windows\\User', Utils\get_home_dir() ); + + putenv( 'HOMEPATH=\\Windows\\User\\HOGE\\' ); + $this->assertSame( 'D:\\Windows\\User\\HOGE', Utils\get_home_dir() ); + + // restore environments + putenv( false === $home ? 'HOME' : "HOME=$home" ); + putenv( false === $homedrive ? 'HOMEDRIVE' : "HOME=$homedrive" ); + putenv( false === $homepath ? 'HOMEPATH' : "HOME=$homepath" ); + } + + public function testTrailingslashit() { + $this->assertSame( 'a/', Utils\trailingslashit( 'a' ) ); + $this->assertSame( 'a/', Utils\trailingslashit( 'a/' ) ); + $this->assertSame( 'a/', Utils\trailingslashit( 'a\\' ) ); + $this->assertSame( 'a/', Utils\trailingslashit( 'a\\//\\' ) ); + } + + public function testNormalizeEols() { + $this->assertSame( "\na\ra\na\n", Utils\normalize_eols( "\r\na\ra\r\na\r\n" ) ); + } + + public function testGetTempDir() { + $this->assertTrue( '/' === substr( Utils\get_temp_dir(), -1 ) ); + } + + public function testHttpRequestBadAddress() { + // Save EE state. + $class_ee_logger = new \ReflectionProperty( 'EE', 'logger' ); + $class_ee_logger->setAccessible( true ); + $class_ee_capture_exit = new \ReflectionProperty( 'EE', 'capture_exit' ); + $class_ee_capture_exit->setAccessible( true ); + + $prev_logger = $class_ee_logger->getValue(); + $prev_capture_exit = $class_ee_capture_exit->getValue(); + + // Enable exit exception. + $class_ee_capture_exit->setValue( true ); + + $logger = new \EE\Loggers\Execution; + EE::set_logger( $logger ); + + $exception = null; + try { + Utils\http_request( 'GET', 'https://nosuchhost_asdf_asdf_asdf.com', null /*data*/, array() /*headers*/, array( 'timeout' => 0.01 ) ); + } catch ( \EE\ExitException $ex ) { + $exception = $ex; + } + $this->assertTrue( null !== $exception ); + $this->assertTrue( 1 === $exception->getCode() ); + $this->assertTrue( empty( $logger->stdout ) ); + $this->assertTrue( false === strpos( $logger->stderr, 'Warning' ) ); + $this->assertTrue( 0 === strpos( $logger->stderr, 'Error: Failed to get url' ) ); + + // Restore. + $class_ee_logger->setValue( $prev_logger ); + $class_ee_capture_exit->setValue( $prev_capture_exit ); + } + + public function testHttpRequestBadCAcert() { + if ( ! extension_loaded( 'curl' ) ) { + $this->markTestSkipped( 'curl not available' ); + } + + // Save EE state. + $class_ee_logger = new \ReflectionProperty( 'EE', 'logger' ); + $class_ee_logger->setAccessible( true ); + + $prev_logger = $class_ee_logger->getValue(); + + $have_bad_cacert = false; + $created_dirs = array(); + + // Hack to create bad CAcert, using Utils\get_vendor_paths() preference for a path as part of a Composer-installed larger project. + $vendor_dir = EE_ROOT . '/../../../vendor'; + $cert_path = '/rmccue/requests/certificates/cacert.pem'; + $bad_cacert_path = $vendor_dir . $cert_path; + if ( ! file_exists( $bad_cacert_path ) ) { + // Capture any directories created so can clean up. + $dirs = array_merge( array( 'vendor' ), array_filter( explode( '/', dirname( $cert_path ) ) ) ); + $current_dir = dirname( $vendor_dir ); + foreach ( $dirs as $dir ) { + if ( ! file_exists( $current_dir . '/' . $dir ) ) { + if ( ! @mkdir( $current_dir . '/' . $dir ) ) { + break; + } + $created_dirs[] = $current_dir . '/' . $dir; + } + $current_dir .= '/' . $dir; + } + if ( $current_dir === dirname( $bad_cacert_path ) && file_put_contents( $bad_cacert_path, "-----BEGIN CERTIFICATE-----\nasdfasdf\n-----END CERTIFICATE-----\n" ) ) { + $have_bad_cacert = true; + } + } + + if ( ! $have_bad_cacert ) { + foreach ( array_reverse( $created_dirs ) as $created_dir ) { + rmdir( $created_dir ); + } + $this->markTestSkipped( 'Unable to create bad CAcert.' ); + } + + $logger = new \EE\Loggers\Execution; + EE::set_logger( $logger ); + + Utils\http_request( 'GET', 'https://example.com' ); + + // Undo bad CAcert hack before asserting. + unlink( $bad_cacert_path ); + foreach ( array_reverse( $created_dirs ) as $created_dir ) { + rmdir( $created_dir ); + } + + $this->assertTrue( empty( $logger->stdout ) ); + $this->assertTrue( 0 === strpos( $logger->stderr, 'Warning: Re-trying without verify after failing to get verified url' ) ); + $this->assertFalse( strpos( $logger->stderr, 'Error' ) ); + + // Restore. + $class_ee_logger->setValue( $prev_logger ); + } + + /** + * @dataProvider dataPastTenseVerb + */ + public function testPastTenseVerb( $verb, $expected ) { + $this->assertSame( $expected, Utils\past_tense_verb( $verb ) ); + } + + public function dataPastTenseVerb() { + return array( + // Known to be used by commands. + array( 'activate', 'activated' ), + array( 'deactivate', 'deactivated' ), + array( 'delete', 'deleted' ), + array( 'import', 'imported' ), + array( 'install', 'installed' ), + array( 'network activate', 'network activated' ), + array( 'network deactivate', 'network deactivated' ), + array( 'regenerate', 'regenerated' ), + array( 'reset', 'reset' ), + array( 'spam', 'spammed' ), + array( 'toggle', 'toggled' ), + array( 'uninstall', 'uninstalled' ), + array( 'update', 'updated' ), + // Some others. + array( 'call', 'called' ), + array( 'check', 'checked' ), + array( 'crop', 'cropped' ), + array( 'fix', 'fixed' ), // One vowel + final "x" excluded. + array( 'ah', 'ahed' ), // One vowel + final "h" excluded. + array( 'show', 'showed' ), // One vowel + final "w" excluded. + array( 'ski', 'skied' ), + array( 'slay', 'slayed' ), // One vowel + final "y" excluded (nearly all irregular anyway). + array( 'submit', 'submited' ), // BUG: multi-voweled verbs that double not catered for - should be "submitted". + array( 'try', 'tried' ), + ); + } + + /** + * @dataProvider dataExpandGlobs + */ + public function testExpandGlobs( $path, $expected ) { + $expand_globs_no_glob_brace = getenv( 'EE_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' ); + + $dir = __DIR__ . '/data/expand_globs/'; + $expected = array_map( function ( $v ) use ( $dir ) { return $dir . $v; }, $expected ); + sort( $expected ); + + putenv( 'EE_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=0' ); + $out = Utils\expand_globs( $dir . $path ); + sort( $out ); + $this->assertSame( $expected, $out ); + + putenv( 'EE_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=1' ); + $out = Utils\expand_globs( $dir . $path ); + sort( $out ); + $this->assertSame( $expected, $out ); + + putenv( false === $expand_globs_no_glob_brace ? 'EE_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' : "EE_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=$expand_globs_no_glob_brace" ); + } + + public function dataExpandGlobs() { + // Files in "data/expand_globs": foo.ab1, foo.ab2, foo.efg1, foo.efg2, bar.ab1, bar.ab2, baz.ab1, baz.ac1, baz.efg2. + return array( + array( 'foo.ab1', array( 'foo.ab1' ) ), + array( '{foo,bar}.ab1', array( 'foo.ab1', 'bar.ab1' ) ), + array( '{foo,baz}.a{b,c}1', array( 'foo.ab1', 'baz.ab1' , 'baz.ac1' ) ), + array( '{foo,baz}.{ab,ac}1', array( 'foo.ab1', 'baz.ab1' , 'baz.ac1' ) ), + array( '{foo,bar}.{ab1,efg1}', array( 'foo.ab1', 'foo.efg1', 'bar.ab1' ) ), + array( '{foo,bar,baz}.{ab,ac,efg}1', array( 'foo.ab1', 'foo.efg1', 'bar.ab1', 'baz.ab1', 'baz.ac1' ) ), + array( '{foo,ba{r,z}}.ab1', array( 'foo.ab1', 'bar.ab1', 'baz.ab1' ) ), + array( '{foo,ba{r,z}}.{ab1,efg1}', array( 'foo.ab1', 'foo.efg1', 'bar.ab1', 'baz.ab1') ), + array( '{foo,bar}.{ab{1,2},efg1}', array( 'foo.ab1', 'foo.ab2', 'foo.efg1', 'bar.ab1', 'bar.ab2' ) ), + array( '{foo,ba{r,z}}.{a{b,c}{1,2},efg{1,2}}', array( 'foo.ab1', 'foo.ab2', 'foo.efg1', 'foo.efg2', 'bar.ab1', 'bar.ab2', 'baz.ab1', 'baz.ac1', 'baz.efg2' ) ), + + array( 'no_such_file', array( 'no_such_file' ) ), // Documenting this behaviour here, which is odd (though advertized) - more natural to return an empty array. + ); + } + + /** + * @dataProvider dataReportBatchOperationResults + */ + public function testReportBatchOperationResults( $stdout, $stderr, $noun, $verb, $total, $successes, $failures, $skips ) { + // Save EE state. + $class_ee_logger = new \ReflectionProperty( 'EE', 'logger' ); + $class_ee_logger->setAccessible( true ); + $class_ee_capture_exit = new \ReflectionProperty( 'EE', 'capture_exit' ); + $class_ee_capture_exit->setAccessible( true ); + + $prev_logger = $class_ee_logger->getValue(); + $prev_capture_exit = $class_ee_capture_exit->getValue(); + + // Enable exit exception. + $class_ee_capture_exit->setValue( true ); + + $logger = new \EE\Loggers\Execution; + EE::set_logger( $logger ); + + $exception = null; + + try { + Utils\report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips ); + } catch ( \EE\ExitException $ex ) { + $exception = $ex; + } + $this->assertSame( $stdout, $logger->stdout ); + $this->assertSame( $stderr, $logger->stderr ); + + // Restore. + $class_ee_logger->setValue( $prev_logger ); + $class_ee_capture_exit->setValue( $prev_capture_exit ); + } + + public function dataReportBatchOperationResults() { + return array( + array( "Success: Noun already verbed.\n", '', 'noun', 'verb', 1, 0, 0, null ), + array( "Success: Verbed 1 of 1 nouns.\n", '', 'noun', 'verb', 1, 1, 0, null ), + array( "Success: Verbed 1 of 2 nouns.\n", '', 'noun', 'verb', 2, 1, 0, null ), + array( "Success: Verbed 2 of 2 nouns.\n", '', 'noun', 'verb', 2, 2, 0, 0 ), + array( "Success: Verbed 1 of 2 nouns (1 skipped).\n", '', 'noun', 'verb', 2, 1, 0, 1 ), + array( "Success: Verbed 2 of 4 nouns (2 skipped).\n", '', 'noun', 'verb', 4, 2, 0, 2 ), + array( '', "Error: No nouns verbed.\n", 'noun', 'verb', 1, 0, 1, null ), + array( '', "Error: No nouns verbed.\n", 'noun', 'verb', 2, 0, 1, null ), + array( '', "Error: No nouns verbed (2 failed).\n", 'noun', 'verb', 3, 0, 2, 0 ), + array( '', "Error: No nouns verbed (2 failed, 1 skipped).\n", 'noun', 'verb', 3, 0, 2, 1 ), + array( '', "Error: Only verbed 1 of 2 nouns.\n", 'noun', 'verb', 2, 1, 1, null ), + array( '', "Error: Only verbed 1 of 3 nouns (2 failed).\n", 'noun', 'verb', 3, 1, 2, 0 ), + array( '', "Error: Only verbed 1 of 6 nouns (3 failed, 2 skipped).\n", 'noun', 'verb', 6, 1, 3, 2 ), + ); + } + + public function testGetPHPBinary() { + $env_php_used = getenv( 'EE_PHP_USED' ); + $env_php = getenv( 'EE_PHP' ); + + putenv( 'EE_PHP_USED' ); + putenv( 'EE_PHP' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertTrue( is_executable( $get_php_binary ) ); + + putenv( 'EE_PHP_USED=/my-php-5.3' ); + putenv( 'EE_PHP' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertSame( $get_php_binary, '/my-php-5.3' ); + + putenv( 'EE_PHP=/my-php-7.3' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertSame( $get_php_binary, '/my-php-5.3' ); // EE_PHP_USED wins. + + putenv( 'EE_PHP_USED' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertSame( $get_php_binary, '/my-php-7.3' ); + + putenv( false === $env_php_used ? 'EE_PHP_USED' : "EE_PHP_USED=$env_php_used" ); + putenv( false === $env_php ? 'EE_PHP' : "EE_PHP=$env_php" ); + } + + /** + * @dataProvider dataProcOpenCompatWinEnv + */ + public function testProcOpenCompatWinEnv( $cmd, $env, $expected_cmd, $expected_env ) { + $env_is_windows = getenv( 'EE_TEST_IS_WINDOWS' ); + + putenv( 'EE_TEST_IS_WINDOWS=1' ); + + $cmd = Utils\_proc_open_compat_win_env( $cmd, $env ); + $this->assertSame( $expected_cmd, $cmd ); + $this->assertSame( $expected_env, $env ); + + putenv( false === $env_is_windows ? 'EE_TEST_IS_WINDOWS' : "EE_TEST_IS_WINDOWS=$env_is_windows" ); + } + + function dataProcOpenCompatWinEnv() { + return array( + array( 'echo', array(), 'echo', array() ), + array( 'ENV=blah echo', array(), 'echo', array( 'ENV' => 'blah' ) ), + array( 'ENV="blah blah" echo', array(), 'echo', array( 'ENV' => 'blah blah' ) ), + array( 'ENV_1="blah1 blah1" ENV_2="blah2" ENV_3=blah3 echo', array(), 'echo', array( 'ENV_1' => 'blah1 blah1', 'ENV_2' => 'blah2', 'ENV_3' => 'blah3' ) ), + array( 'ENV= echo', array(), 'echo', array( 'ENV' => '' ) ), + array( 'ENV=0 echo', array(), 'echo', array( 'ENV' => '0' ) ), + + // With `$env` set. + array( 'echo', array( 'ENV' => 'in' ), 'echo', array( 'ENV' => 'in' ) ), + array( 'ENV=blah echo', array( 'ENV_1' => 'in1', 'ENV_2' => 'in2' ), 'echo', array( 'ENV_1' => 'in1', 'ENV_2' => 'in2', 'ENV' => 'blah' ) ), + array( 'ENV="blah blah" echo', array( 'ENV' => 'in' ), 'echo', array( 'ENV' => 'blah blah' ) ), + + // Special cases. + array( '1=1 echo', array(), '1=1 echo', array() ), // Must begin with alphabetic or underscore. + array( '_eNv=1 echo', array(), 'echo', array( '_eNv' => '1' ) ), // Mixed-case and beginning with underscore allowed. + array( 'ENV=\'blah blah\' echo', array(), 'blah\' echo', array( 'ENV' => '\'blah' ) ), // Unix escaping not supported, ie treated literally. + ); + } + + /** @dataProvider dataIsJson */ + public function testIsJson( $argument, $ignore_scalars, $expected ) { + $this->assertEquals( $expected, Utils\is_json( $argument, $ignore_scalars ) ); + } + + public function dataIsJson() { + return array( + array( '42', true, false ), + array( '42', false, true ), + array( '"test"', true, false ), + array( '"test"', false, true ), + array( '{"key1":"value1","key2":"value2"}', true, true ), + array( '{"key1":"value1","key2":"value2"}', false, true ), + array( '["value1","value2"]', true, true ), + array( '["value1","value2"]', false, true ), + array( '0', true, false ), + array( '0', false, true ), + array( '', true, false ), + array( '', false, false ), + ); + } + + /** @dataProvider dataParseShellArray */ + public function testParseShellArray( $assoc_args, $array_arguments, $expected ) { + $this->assertEquals( $expected, Utils\parse_shell_arrays( $assoc_args, $array_arguments ) ); + } + + public function dataParseShellArray() { + return array( + array( array( 'alpha' => '{"key":"value"}' ), array(), array( 'alpha' => '{"key":"value"}' ) ), + array( array( 'alpha' => '{"key":"value"}' ), array( 'alpha' ), array( 'alpha' => array( 'key' => 'value' ) ) ), + array( array( 'alpha' => '{"key":"value"}' ), array( 'beta' ), array( 'alpha' => '{"key":"value"}' ) ), + ); + } +} diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 000000000..2abab7c5a --- /dev/null +++ b/utils/README.md @@ -0,0 +1,4 @@ +Utils +===== + +The scripts for phar generation ee command auto-completion. \ No newline at end of file diff --git a/utils/amp-paths.txt b/utils/amp-paths.txt new file mode 100644 index 000000000..a6c891d3b --- /dev/null +++ b/utils/amp-paths.txt @@ -0,0 +1,5 @@ +/Applications/MAMP/bin/php/php5.3*/bin/php +/Applications/MAMP/bin/php5*/bin/php +/Applications/MAMP/bin/php/php5.[34]*/bin/php +/Applications/xampp/xamppfiles/bin/php; +/opt/lampp/bin/php diff --git a/utils/auto-composer-update.sh b/utils/auto-composer-update.sh new file mode 100755 index 000000000..e3fa84dd9 --- /dev/null +++ b/utils/auto-composer-update.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +### +# Runs composer update, commits changes to a new branch, +# and creates a pull request. +# +# Requires git, composer, and hub +### + +date + +if [ -z "$EE_DIR" ]; then + echo 'Please set $EE_DIR' + exit 1 +fi + +set -ex + +cd $EE_DIR + +# Reset EE directory to baseline +git checkout -f master +git pull origin master +composer install +# Run composer update and capture to untracked log file +composer update --no-progress --no-interaction |& tee vendor/update.log +UPDATE=$(cat vendor/update.log | col -b) + +# We only care to proceed when there are changes +if [ -z "$(git status -s)" ]; then + echo 'No updates available' + exit 0; +fi + +# Create a dated branch and commit the changes +DATE=$(date +%Y-%m-%d) +BRANCH="update-deps-$DATE" +git branch -f $BRANCH master +git checkout $BRANCH +git add . +MESSAGE="Update Composer dependencies ($DATE) + +\`\`\` +$UPDATE +\`\`\`" +git commit -m "$MESSAGE" + +# Push and pull request +git push origin $BRANCH +hub pull-request -m "$MESSAGE" diff --git a/utils/changelog.sh b/utils/changelog.sh new file mode 100644 index 000000000..15c5c6496 --- /dev/null +++ b/utils/changelog.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +echo -e "## What's Changed\n" > changelog.txt + +# Get the creation date of the most recent release +createdAt=$(gh api graphql -F owner='EasyEngine' -F name='easyengine' -f query=' + query { + repository(owner: "EasyEngine", name: "easyengine") { + releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) { + nodes { tagName, createdAt } + } + } + } +' | jq -r '.data.repository.releases.nodes[0].createdAt') + +# Also get the tag name for verification +tagName=$(gh api graphql -F owner='EasyEngine' -F name='easyengine' -f query=' + query { + repository(owner: "EasyEngine", name: "easyengine") { + releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) { + nodes { tagName, createdAt } + } + } + } +' | jq -r '.data.repository.releases.nodes[0].tagName') + +echo "Last release: $tagName ($createdAt)" + +# Search for merged PRs since the last release with proper pagination +# Using merged:>$createdAt instead of updated:>$createdAt +gh api graphql --paginate -f query=" +query(\$endCursor: String) { + search(query: \"org:EasyEngine merged:>$createdAt is:pr is:merged\", type: ISSUE, first: 100, after: \$endCursor) { + repositoryCount + pageInfo { + hasNextPage + endCursor + } + edges { + node { + ... on PullRequest { + title + permalink + state + author { + login + } + mergedAt + repository { + name + } + } + } + } + } +} +" --template '{{range .data.search.edges}}{{"* "}}{{.node.title}} {{.node.permalink}}{{" @"}}{{.node.author.login}}{{"\n"}}{{end}}' >> changelog.txt diff --git a/utils/ee-completion.bash b/utils/ee-completion.bash new file mode 100644 index 000000000..cd2bee805 --- /dev/null +++ b/utils/ee-completion.bash @@ -0,0 +1,23 @@ +# bash completion for the `ee` command + +_ee_complete() { + local OLD_IFS="$IFS" + local cur=${COMP_WORDS[COMP_CWORD]} + + IFS=$'\n'; # want to preserve spaces at the end + local opts="$(ee cli completions --line="$COMP_LINE" --point="$COMP_POINT")" + + if [[ "$opts" =~ \\s* ]] + then + COMPREPLY=( $(compgen -f -- $cur) ) + elif [[ $opts = "" ]] + then + COMPREPLY=( $(compgen -f -- $cur) ) + else + COMPREPLY=( ${opts[*]} ) + fi + + IFS="$OLD_IFS" + return 0 +} +complete -o nospace -F _ee_complete ee \ No newline at end of file diff --git a/utils/ee-completion.zsh b/utils/ee-completion.zsh new file mode 100644 index 000000000..065447f36 --- /dev/null +++ b/utils/ee-completion.zsh @@ -0,0 +1,101 @@ +#compdef ee + +function _ee { + + _ee_completion() { + ee_completion=() + ee_completion=$(sudo /usr/local/bin/ee cli completions --shell='zsh' --line="ee $current_command " --point="$current_position") + + completion=() + while read line; do + completion+=("${line}") + done < <(echo "$ee_completion") + } + + _ee_service_list() { + ee_services=$(sudo docker-compose -f /opt/easyengine/services/docker-compose.yml ps --services | sed 's/global\-//g') + completion=() + while read line; do + completion+=("${line}") + done < <(echo "$ee_services") + } + + _ee_site_list() { + ee_sites=$(sqlite3 /opt/easyengine/db/ee.sqlite "select site_url from sites;") + completion=() + while read line; do + completion+=("${line}") + done < <(echo "$ee_sites") + } + + if [[ "${words[2]}" == "cli" || "${words[2]}" == "config" || "${words[2]}" == "help" ]]; then + _arguments '1: :-> sub_commands' '2: :->sub_command_param' '*: :->flags' + + elif [[ "${words[2]}" == "service" ]]; then + _arguments '1: :-> sub_commands' '2: :->sub_command_param' '3: :->get_service_name' '*: :->flags' + + elif [[ "${words[2]}" == "shell" ]]; then + _arguments '1: :-> sub_commands' '2: :->get_site_name' '*: :->flags' + + elif [[ `pwd` == /opt/easyengine/sites*/* ]]; then + _arguments '1: :-> sub_commands' '2: :->sub_command_param' '*: :->flags' + + elif [[ "${words[2]}" == "site" && "${words[3]}" == "create" ]]; then + _arguments '1: :-> sub_commands' '2: :->sub_command_param' '*: :->flags' + + else + _arguments '1: :-> sub_commands' '2: :->sub_command_param' '3: :->get_site_name' '*: :->flags' + fi + + current_command="" + current_position=0 + + case $state in + sub_commands) + + current_command=${words[@]:1} + current_position=$((CURRENT + 1)) + + _ee_completion + _describe 'command' completion + ;; + + sub_command_param) + + chrlen="ee ${words[@]:1}" + current_command=${words[@]:1} + current_position=$((${#chrlen})) + + _ee_completion + + _describe 'command' completion + ;; + + get_site_name) + + _ee_site_list + _describe 'command' completion + ;; + + get_service_name) + + _ee_service_list + _describe 'command' completion + ;; + + flags) + + chrlen="ee ${words[@]:1}" + current_command=${words[@]:1} + current_position=$((${#chrlen})) + + _ee_completion + _describe 'command' completion + + ;; + + *) + echo "Error occured in auto completion" + ;; + esac +} diff --git a/utils/find-php b/utils/find-php new file mode 100755 index 000000000..f8d19f4a2 --- /dev/null +++ b/utils/find-php @@ -0,0 +1,19 @@ +#!/usr/bin/env sh + +# Special case for *AMP installers, since they normally don't set themselves +# as the default cli php out of the box. +for amp_php in $(cat $(dirname $0)/amp-paths.txt); do + if [ -x $amp_php ]; then + echo $amp_php + exit + fi +done + +which php || which php-cli + +if [ $? -eq 0 ]; then + exit +fi + +echo "no PHP binary found" >&2 +exit 1 diff --git a/utils/get-package-require-from-composer.php b/utils/get-package-require-from-composer.php new file mode 100644 index 000000000..33e30ccfd --- /dev/null +++ b/utils/get-package-require-from-composer.php @@ -0,0 +1,23 @@ +autoload->files ) ) { + echo 'composer.json must specify valid "autoload" => "files"'; + exit(1); +} + +echo implode( PHP_EOL, $composer->autoload->files ); +exit(0); \ No newline at end of file diff --git a/utils/make-phar-spec.php b/utils/make-phar-spec.php new file mode 100644 index 000000000..2a2ad03f9 --- /dev/null +++ b/utils/make-phar-spec.php @@ -0,0 +1,37 @@ + array( + 'runtime' => '=', + 'file' => '', + 'desc' => 'Path to output file', + ), + + 'version' => array( + 'runtime' => '=', + 'file' => '', + 'desc' => 'New package version', + ), + + 'store-version' => array( + 'runtime' => '', + 'file' => '', + 'default' => false, + 'desc' => 'If true the contents of ./VERSION will be set to the value passed to --version', + ), + + 'quiet' => array( + 'runtime' => '', + 'file' => '', + 'default' => false, + 'desc' => 'Suppress informational messages', + ), + + 'build' => array( + 'runtime' => '=', + 'file' => '', + 'default' => '', + 'desc' => 'Create a minimum test build "cli", that only supports cli commands', + ), +); + diff --git a/utils/make-phar.php b/utils/make-phar.php new file mode 100644 index 000000000..4429f36bc --- /dev/null +++ b/utils/make-phar.php @@ -0,0 +1,346 @@ +parse_args( array_slice( $GLOBALS['argv'], 1 ) ); + +if ( ! isset( $args[0] ) || empty( $args[0] ) ) { + fwrite( STDERR, "usage: php -dphar.readonly=0 $argv[0] [--quiet] [--version=same|patch|minor|major|x.y.z] [--store-version] [--build=cli]" . PHP_EOL ); + exit(1); +} + +define( 'DEST_PATH', $args[0] ); + +define( 'BE_QUIET', isset( $runtime_config['quiet'] ) && $runtime_config['quiet'] ); + +define( 'BUILD', isset( $runtime_config['build'] ) ? $runtime_config['build'] : '' ); + +$current_version = trim( file_get_contents( EE_ROOT . '/VERSION' ) ); + +if ( isset( $runtime_config['version'] ) ) { + $new_version = $runtime_config['version']; + $new_version = Utils\increment_version( $current_version, $new_version ); + + if ( isset( $runtime_config['store-version'] ) && $runtime_config['store-version'] ) { + file_put_contents( EE_ROOT . '/VERSION', $new_version ); + } + + $current_version = $new_version; +} + +function add_file( $phar, $path ) { + $key = str_replace( EE_BASE_PATH, '', $path ); + + if ( ! BE_QUIET ) { + echo "$key - $path" . PHP_EOL; + } + + $basename = basename( $path ); + if ( 0 === strpos( $basename, 'autoload_' ) && preg_match( '/(?:classmap|files|namespaces|psr4|static)\.php$/', $basename ) ) { + // Strip autoload maps of unused stuff. + static $strip_res = null; + if ( null === $strip_res ) { + if ( 'cli' === BUILD ) { + $strips = array( + '\/(?:behat|composer|gherkin)\/src\/', + '\/phpunit\/', + '\/nb\/oxymel\/', + '-command\/src\/', + '\/ee\/[^\n]+?-command\/', + '\/symfony\/(?!finder|polyfill-mbstring|polyfill-ctype|polyfill-php81|polyfill-php80|polyfill-php73)[^\/]+\/', + '\/(?:dealerdirect|squizlabs|wimg)\/', + ); + } else { + $strips = array( + '\/(?:behat|gherkin)\/src\/', + '\/phpunit\/', + '\/symfony\/(?!console|filesystem|finder|polyfill-mbstring|polyfill-ctype|polyfill-php81|polyfill-php80|polyfill-php73|process|serializer|service-contracts)', + '\/composer\/spdx-licenses\/', + '\/Composer\/(?:Command\/|Compiler\.php|Console\/|Downloader\/Pear|Installer\/Pear|Question\/|Repository\/Pear|SelfUpdate\/)', + '\/(?:dealerdirect|squizlabs|wimg)\/', + ); + } + $strip_res = array_map( function ( $v ) { + return '/^[^,\n]+?' . $v . '[^,\n]+?, *\n/m'; + }, $strips ); + } + $phar[ $key ] = preg_replace( $strip_res, '', file_get_contents( $path ) ); + } else { + $phar[ $key ] = file_get_contents( $path ); + } +} + +function set_file_contents( $phar, $path, $content ) { + $key = str_replace( EE_BASE_PATH, '', $path ); + + if ( ! BE_QUIET ) { + echo "$key - $path" . PHP_EOL; + } + + $phar[ $key ] = $content; +} + +function get_composer_versions( $current_version ) { + $composer_lock_path = EE_ROOT . '/composer.lock'; + if ( ! ( $get_composer_lock = file_get_contents( $composer_lock_path ) ) || ! ( $composer_lock = json_decode( $get_composer_lock, true ) ) ) { + fwrite( STDERR, sprintf( "Warning: Failed to read '%s'." . PHP_EOL, $composer_lock_path ) ); + return ''; + } + if ( ! isset( $composer_lock['packages'] ) ) { + fwrite( STDERR, sprintf( "Warning: No packages in '%s'." . PHP_EOL, $composer_lock_path ) ); + return ''; + } + $vendor_versions = array( implode( ' ', array( 'easyengine/ee', $current_version, date( 'c' ) ) ) ); + $missing_names = $missing_versions = $missing_references = 0; + foreach ( $composer_lock['packages'] as $package ) { + if ( isset( $package['name'] ) ) { + $vendor_version = array( $package['name'] ); + if ( isset( $package['version'] ) ) { + $vendor_version[] = $package['version']; + } else { + $vendor_version[] = 'unknown_version'; + $missing_versions++; + } + if ( isset( $package['source'] ) && isset( $package['source']['reference'] ) ) { + $vendor_version[] = $package['source']['reference']; + } elseif( isset( $package['dist'] ) && isset( $package['dist']['reference'] ) ) { + $vendor_version[] = $package['dist']['reference']; + } else { + $vendor_version[] = 'unknown_reference'; + $missing_references++; + } + $vendor_versions[] = implode( ' ', $vendor_version ); + } else { + $vendor_versions[] = implode( ' ', array( 'unknown_package', 'unknown_version', 'unknown_reference' ) ); + $missing_names++; + } + } + if ( $missing_names ) { + fwrite( STDERR, sprintf( "Warning: %d package names missing from '%s'." . PHP_EOL, $missing_names, $composer_lock_path ) ); + } + if ( $missing_versions ) { + fwrite( STDERR, sprintf( "Warning: %d package versions missing from '%s'." . PHP_EOL, $missing_versions, $composer_lock_path ) ); + } + if ( $missing_references ) { + fwrite( STDERR, sprintf( "Warning: %d package references missing from '%s'." . PHP_EOL, $missing_references, $composer_lock_path ) ); + } + return implode( "\n", $vendor_versions ); +} + +if ( file_exists( DEST_PATH ) ) { + unlink( DEST_PATH ); +} +$phar = new Phar( DEST_PATH, 0, 'ee.phar' ); + +$phar->startBuffering(); + +// PHP files +$finder = new Finder(); +$finder + ->files() + ->ignoreVCS(true) + ->name('*.php') + ->in(EE_ROOT . '/php') + ->in(EE_ROOT . '/migrations') + ->in(EE_VENDOR_DIR . '/mustache') + ->in(EE_VENDOR_DIR . '/rmccue/requests') + ->in(EE_VENDOR_DIR . '/react') + ->in(EE_VENDOR_DIR . '/composer') + ->in(EE_VENDOR_DIR . '/cloudflare') + ->in(EE_VENDOR_DIR . '/symfony/finder') + ->in(EE_VENDOR_DIR . '/symfony/polyfill-mbstring') + ->in(EE_VENDOR_DIR . '/symfony/polyfill-ctype') + ->in(EE_VENDOR_DIR . '/symfony/polyfill-php73') + ->in(EE_VENDOR_DIR . '/symfony/polyfill-php80') + ->in(EE_VENDOR_DIR . '/symfony/polyfill-php81') + ->in(EE_VENDOR_DIR . '/monolog') + ->in(EE_VENDOR_DIR . '/daverandom') + ->in(EE_VENDOR_DIR . '/guzzlehttp') + ->in(EE_VENDOR_DIR . '/ralouphie/getallheaders') + ->in(EE_VENDOR_DIR . '/acmephp') + ->in(EE_VENDOR_DIR . '/league') + ->in(EE_VENDOR_DIR . '/webmozart') + ->notName('behat-tags.php') + ->notPath('#(?:[^/]+-command|php-cli-tools)/vendor/#') // For running locally, in case have composer installed or symlinked them. + ->exclude('examples') + ->exclude('features') + ->exclude('test') + ->exclude('tests') + ->exclude('Test') + ->exclude('Tests'); + +if ( 'cli' === BUILD ) { + $finder + ->in(EE_VENDOR_DIR . '/wp-cli/mustangostang-spyc') + ->in(EE_VENDOR_DIR . '/wp-cli/php-cli-tools') + ->in(EE_VENDOR_DIR . '/seld/cli-prompt') + ->exclude('composer/ca-bundle') + ->exclude('composer/semver') + ->exclude('composer/src') + ->exclude('composer/spdx-licenses') + ; +} else { + $finder + ->in(EE_VENDOR_DIR . '/easyengine') + ->in(EE_VENDOR_DIR . '/wp-cli') + ->in(EE_VENDOR_DIR . '/psr') + ->in(EE_VENDOR_DIR . '/seld') + ->in(EE_VENDOR_DIR . '/symfony/console') + ->in(EE_VENDOR_DIR . '/symfony/filesystem') + ->in(EE_VENDOR_DIR . '/symfony/process') + ->in(EE_VENDOR_DIR . '/symfony/serializer') + ->in(EE_VENDOR_DIR . '/symfony/service-contracts') + ->in(EE_VENDOR_DIR . '/justinrainbow/json-schema') + ->exclude('demo') + ->exclude('nb/oxymel/OxymelTest.php') + ->exclude('composer/spdx-licenses') + ->exclude('composer/composer/src/Composer/Command') + ->exclude('composer/composer/src/Composer/Compiler.php') + ->exclude('composer/composer/src/Composer/Console') + ->exclude('composer/composer/src/Composer/Downloader/PearPackageExtractor.php') // Assuming Pear installation isn't supported by ee. + ->exclude('composer/composer/src/Composer/Installer/PearBinaryInstaller.php') + ->exclude('composer/composer/src/Composer/Installer/PearInstaller.php') + ->exclude('composer/composer/src/Composer/Question') + ->exclude('composer/composer/src/Composer/Repository/Pear') + ->exclude('composer/composer/src/Composer/SelfUpdate') + ; +} + +foreach ( $finder as $file ) { + add_file( $phar, $file ); +} + +$finder = new Finder(); +$finder + ->files() + ->ignoreVCS(true) + ->name('img-versions.json') + ->in( EE_ROOT ) + ->exclude( EE_ROOT . 'php/') + ->exclude( EE_ROOT . 'templates/') + ->exclude( EE_ROOT . 'vendor/') +; + +foreach ( $finder as $file ) { + add_file( $phar, $file ); +} + +$finder = new Finder(); + +$finder + ->files() + ->ignoreDotFiles(false) + ->in(EE_VENDOR_DIR . '/easyengine/*-command/templates') + ->in(EE_VENDOR_DIR . '/easyengine/site-type-*/templates') + ->name('*.mustache') + ->name('.env.mustache') + ->name('*.zip'); + +$finder + ->files() + ->ignoreDotFiles(false) + ->in(EE_VENDOR_DIR . '/easyengine/*/migrations') + ->name('*.php'); + +foreach ( $finder as $file ) { + add_file( $phar, $file ); +} + +$finder + ->files() + ->ignoreDotFiles(false) + ->in(EE_VENDOR_DIR . '/easyengine/*-command/') + ->name('ee-*.json'); + +foreach ( $finder as $file ) { + add_file( $phar, $file ); +} + +// other files +$finder = new Finder(); +$finder->files() + ->ignoreVCS(true) + ->ignoreDotFiles(false) + ->in( EE_ROOT . '/templates'); + +foreach ( $finder as $file ) { + add_file( $phar, $file ); +} + +if ( 'cli' !== BUILD ) { + // Include base project files, because the autoloader will load them + if ( EE_BASE_PATH !== EE_ROOT ) { + $finder = new Finder(); + $finder + ->files() + ->ignoreVCS(true) + ->name('*.php') + ->in(EE_BASE_PATH . '/src') + ->exclude('test') + ->exclude('tests') + ->exclude('Test') + ->exclude('Tests'); + foreach ( $finder as $file ) { + add_file( $phar, $file ); + } + // Any PHP files in the project root + foreach ( glob( EE_BASE_PATH . '/*.php' ) as $file ) { + add_file( $phar, $file ); + } + } + + foreach ( $finder as $file ) { + add_file( $phar, $file ); + } +} + +add_file( $phar, EE_VENDOR_DIR . '/autoload.php' ); +if ( 'cli' !== BUILD ) { + add_file( $phar, EE_VENDOR_DIR . '/composer/composer/LICENSE' ); + add_file( $phar, EE_VENDOR_DIR . '/composer/composer/res/composer-schema.json' ); +} +add_file( $phar, EE_VENDOR_DIR . '/rmccue/requests/certificates/cacert.pem' ); + +set_file_contents( $phar, EE_ROOT . '/COMPOSER_VERSIONS', get_composer_versions( $current_version ) ); +set_file_contents( $phar, EE_ROOT . '/VERSION', $current_version ); + +$phar_boot = str_replace( EE_BASE_PATH, '', EE_ROOT . '/php/boot-phar.php' ); +$phar->setStub( << +EOB +); + +$phar->stopBuffering(); + +chmod( DEST_PATH, 0755 ); // Make executable. + +if ( ! BE_QUIET ) { + echo "Generated " . DEST_PATH . PHP_EOL; +} diff --git a/utils/test-phar-download b/utils/test-phar-download new file mode 100755 index 000000000..f23c24c39 --- /dev/null +++ b/utils/test-phar-download @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +actual_checksum=$(curl http://ee.org/packages/phar/ee.phar | md5sum | cut -d ' ' -f 1) + +echo "expected:" $(curl -s http://ee.org/packages/phar/ee.phar.md5) +echo "actual: " $actual_checksum + +actual_checksum=$(curl http://ee.org/packages/phar/ee.phar | sha512sum | cut -d ' ' -f 1) + +echo "expected SHA-512:" $(curl -s http://ee.org/packages/phar/ee.phar.sha512) +echo "actual SHA-512: " $actual_checksum diff --git a/utils/update-phar b/utils/update-phar new file mode 100755 index 000000000..e0e4f8334 --- /dev/null +++ b/utils/update-phar @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -ex + +version=${1-"same"} + +packages_repo=../ee-packages +mkdir -p "$packages_repo/phar" + +fname="phar/ee.phar" + +# generate archive +php -dphar.readonly=0 ./utils/make-phar.php $packages_repo/$fname --quiet --version=$version --store-version + +cd $packages_repo + +# smoke test +php $fname cli version