diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..f4cab784eb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +/vendor +/docs \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..6d70ac906d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# This file is for unifying the coding style for different editors and IDEs. +# More information at http://EditorConfig.org + +# Set this file as the root +root = true + +# Global settings for all files +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# PHP files +[*.php] +indent_size = 4 + +# Frontend files +[*.{css,html,js,less,sass,scss}] +indent_size = 2 diff --git a/.github/workflows/composer-validate.yml b/.github/workflows/composer-validate.yml index 869b13d00d..07bd51ee4e 100644 --- a/.github/workflows/composer-validate.yml +++ b/.github/workflows/composer-validate.yml @@ -6,8 +6,8 @@ name: Validate Composer dependencies on: pull_request: branches: - - alpha - + - 'develop' + jobs: build-test: runs-on: ubuntu-latest diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index ac272915bc..62b2613362 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -7,19 +7,18 @@ on: # only trigger CI when pull request on following branches pull_request: branches: - - 'alpha' + - 'develop' # this is to manually trigger the worklow workflow_dispatch: inputs: logLevel: - description: 'Reason' + description: 'Reason' default: 'Manual launch' jobs: buildAndTestDockerImg: runs-on: ubuntu-latest steps: - - name: Check Out Repo # Check out the repo, using current branch # https://github.com/marketplace/actions/checkout @@ -35,6 +34,7 @@ jobs: docker run -p 80:80 -d --rm --name jeedom_bullseye jeedom:bullseye sleep 45 docker exec jeedom_bullseye php sick.php + - name: Build jeedom:bookworm # build current image for bookworm run: | @@ -45,7 +45,7 @@ jobs: docker run -p 81:80 -d --rm --name jeedom_bookworm jeedom:bookworm sleep 45 docker exec jeedom_bookworm php sick.php - + - name: Clean docker image continue-on-error: true run: | diff --git a/.github/workflows/phpstan.yaml b/.github/workflows/phpstan.yaml index 8d90800544..be829bf51c 100644 --- a/.github/workflows/phpstan.yaml +++ b/.github/workflows/phpstan.yaml @@ -2,29 +2,31 @@ name: PHPStan on: push: - branches: [ alpha ] + branches: [ develop ] pull_request: - branches: [ alpha ] + branches: [ develop ] jobs: phpstan: runs-on: ubuntu-latest + # define every php version to test + strategy: + # do not stop at first fail + fail-fast: false + matrix: + php: [7.4, 8.2] + steps: - uses: actions/checkout@v4 - - name: Setup PHP 7.4 for dependencies + - name: Setup PHP ${{ matrix.php }} for dependencies uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: ${{ matrix.php }} - name: Install Dependencies run: composer update --ignore-platform-reqs - - name: Setup PHP 8.2 for PHPStan - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - - name: Restore PHPStan cache id: cache-phpstan uses: actions/cache/restore@v3 @@ -32,16 +34,12 @@ jobs: path: phpstan.phar key: phpstan-1 - - name: Download PHPStan - if: steps.cache-phpstan.outputs.cache-hit != 'true' - run: wget https://github.com/phpstan/phpstan/releases/latest/download/phpstan.phar - - name: Run PHPStan - run: php phpstan.phar analyse --configuration phpstan.neon + run: vendor/bin/phpstan analyse --configuration phpstan.neon update-baseline: needs: phpstan - if: github.event_name == 'push' && github.ref == 'refs/heads/alpha' + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -50,7 +48,7 @@ jobs: - name: Delete existing branch if exists run: | - if git ls-remote --heads origin update-phpstan-baseline | grep update-phpstan-baseline; then + if git ls-remote --heads origin update-phpstan-baseline | grep -q update-phpstan-baseline; then git push origin --delete update-phpstan-baseline fi @@ -62,19 +60,11 @@ jobs: - name: Install Dependencies run: composer update --ignore-platform-reqs - - name: Setup PHP 8.2 for PHPStan - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - - - name: Download PHPStan - run: wget https://github.com/phpstan/phpstan/releases/latest/download/phpstan.phar - - name: Generate new baseline id: generate-baseline run: | cp phpstan-baseline.neon phpstan-baseline.neon.old - php phpstan.phar analyse --configuration phpstan.neon --generate-baseline + vendor/bin/phpstan analyse --configuration phpstan.neon --generate-baseline if ! diff -q phpstan-baseline.neon phpstan-baseline.neon.old > /dev/null; then echo "baseline_changed=true" >> $GITHUB_OUTPUT fi @@ -87,10 +77,10 @@ jobs: title: '[CI] Update PHPStan baseline' body: | Mise à jour automatique du baseline PHPStan suite à la correction d'erreurs. - + Cette PR a été générée automatiquement par le workflow CI/CD. branch: update-phpstan-baseline - base: alpha + base: develop delete-branch: true add-paths: | phpstan-baseline.neon diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml new file mode 100644 index 0000000000..e27b7cd9dd --- /dev/null +++ b/.github/workflows/translations.yml @@ -0,0 +1,18 @@ +name: translate + +on: + workflow_dispatch: + push: + branches: + - develop + +jobs: + auto-translate: + runs-on: ubuntu-latest + steps: + - uses: jeedom/jeetranslate@main + with: + deepl_api_key: ${{ secrets.DEEPL_API_KEY }} + include_empty_translation: false + target_languages: "en_US,es_ES,de_DE,it_IT,pt_PT" + use_core_translations: true \ No newline at end of file diff --git a/.github/workflows/work.yml b/.github/workflows/work.yml index 9db6932f30..960eb4e76c 100644 --- a/.github/workflows/work.yml +++ b/.github/workflows/work.yml @@ -2,12 +2,10 @@ on: # Workflows check plugin Jeedom push: branches: - - alpha - - beta + - develop pull_request: branches: - - alpha - - beta + - develop - master name : 'Test Core Jeedom' diff --git a/Dockerfile b/Dockerfile index 37a75bffed..4628ef8761 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,18 +44,18 @@ RUN apt -o Dpkg::Options::="--force-confdef" -y install software-properties-comm php libapache2-mod-php php-json php-mysql php-curl php-gd php-imap php-xml php-opcache php-soap php-xmlrpc \ php-common php-dev php-zip php-ssh2 php-mbstring php-ldap php-yaml php-snmp && apt -y remove brltty -COPY install/install.sh /tmp/ -RUN sh /tmp/install.sh -s 1 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 2 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 3 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 4 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 5 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +COPY --chown=root:root --chmod=550 install/install.sh /root/ +RUN sh /root/install.sh -s 1 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 2 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 3 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 4 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 5 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker COPY . ${WEBSERVER_HOME} -RUN sh /tmp/install.sh -s 7 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 8 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 9 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 10 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker -RUN sh /tmp/install.sh -s 11 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 7 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 8 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 9 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 10 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker +RUN sh /root/install.sh -s 11 -v ${VERSION} -w ${WEBSERVER_HOME} -d ${DATABASE} -i docker RUN apt-get clean && rm -rf /var/lib/apt/lists/* RUN echo >${WEBSERVER_HOME}/initialisation diff --git a/composer.json b/composer.json index 25d7fc3830..8bd5350b62 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "require": { + "abbadon1334/sun-position-spa-php" : "^2", "dragonmantank/cron-expression": "^3", "symfony/expression-language": "5 - 7", "pragmarx/google2fa-qrcode": "^3", @@ -15,5 +16,8 @@ "platform": { "php": "7.4" } + }, + "require-dev": { + "phpstan/phpstan": "^2.1" } } diff --git a/composer.lock b/composer.lock index b78c8572c4..96e13482b3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,59 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6d3817ee337cc5ed162b1e9463030e8a", + "content-hash": "6f406f5b318ce2f2efbbed9597706668", "packages": [ + { + "name": "abbadon1334/sun-position-spa-php", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/abbadon1334/sun-position-spa-php.git", + "reference": "26244d51284ae80061dc21f26d4b1c31672354bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/abbadon1334/sun-position-spa-php/zipball/26244d51284ae80061dc21f26d4b1c31672354bf", + "reference": "26244d51284ae80061dc21f26d4b1c31672354bf", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "codacy/coverage": "dev-master", + "friendsofphp/php-cs-fixer": "dev-master@dev", + "phpmd/phpmd": "2.6.0", + "phpmetrics/phpmetrics": "dev-master@dev", + "phpstan/phpstan": "0.11.5", + "phpunit/phpunit": "*", + "squizlabs/php_codesniffer": "3.4.2", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SolarData\\": "src/", + "SolarData\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Francesco Danti", + "email": "fdanti@gmail.com" + } + ], + "description": "solar data calculation and sun position", + "support": { + "issues": "https://github.com/abbadon1334/sun-position-spa-php/issues", + "source": "https://github.com/abbadon1334/sun-position-spa-php/tree/master" + }, + "time": "2019-07-08T09:51:15+00:00" + }, { "name": "bacon/bacon-qr-code", "version": "2.0.8", @@ -128,16 +179,16 @@ }, { "name": "dasprid/enum", - "version": "1.0.6", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90" + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90", - "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", "shasum": "" }, "require": { @@ -172,35 +223,34 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.6" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" }, - "time": "2024-08-09T14:30:48+00:00" + "time": "2025-09-16T12:23:56+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "8c784d071debd117328803d86b2097615b457500" + "reference": "1b2de7f4a468165dca07b142240733a1973e766d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", - "reference": "8c784d071debd117328803d86b2097615b457500", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/1b2de7f4a468165dca07b142240733a1973e766d", + "reference": "1b2de7f4a468165dca07b142240733a1973e766d", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "webmozart/assert": "^1.0" + "php": "^7.2|^8.0" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" }, "type": "library", "extra": { @@ -231,7 +281,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.5.0" }, "funding": [ { @@ -239,20 +289,20 @@ "type": "github" } ], - "time": "2024-10-09T13:47:03+00:00" + "time": "2025-10-31T18:36:32+00:00" }, { "name": "influxdata/influxdb-client-php", - "version": "3.6.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/influxdata/influxdb-client-php.git", - "reference": "3606b12214508f22126b7ed0565d53380674312a" + "reference": "59ac11d63ce030973c79d5b05797813761a0d58e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/influxdata/influxdb-client-php/zipball/3606b12214508f22126b7ed0565d53380674312a", - "reference": "3606b12214508f22126b7ed0565d53380674312a", + "url": "https://api.github.com/repos/influxdata/influxdb-client-php/zipball/59ac11d63ce030973c79d5b05797813761a0d58e", + "reference": "59ac11d63ce030973c79d5b05797813761a0d58e", "shasum": "" }, "require": { @@ -287,22 +337,22 @@ ], "support": { "issues": "https://github.com/influxdata/influxdb-client-php/issues", - "source": "https://github.com/influxdata/influxdb-client-php/tree/3.6.0" + "source": "https://github.com/influxdata/influxdb-client-php/tree/3.8.0" }, - "time": "2024-06-24T10:01:53+00:00" + "time": "2025-06-26T05:12:59+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.7.0", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226", + "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226", "shasum": "" }, "require": { @@ -356,7 +406,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2024-05-08T12:18:48+00:00" + "time": "2025-09-24T15:12:37+00:00" }, { "name": "php-http/client-common", @@ -738,21 +788,21 @@ }, { "name": "pragmarx/google2fa-qrcode", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa-qrcode.git", - "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b" + "reference": "c23ebcc3a50de0d1566016a6dd1486e183bb78e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/ce4d8a729b6c93741c607cfb2217acfffb5bf76b", - "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/c23ebcc3a50de0d1566016a6dd1486e183bb78e1", + "reference": "c23ebcc3a50de0d1566016a6dd1486e183bb78e1", "shasum": "" }, "require": { "php": ">=7.1", - "pragmarx/google2fa": ">=4.0" + "pragmarx/google2fa": "^4.0|^5.0|^6.0|^7.0|^8.0" }, "require-dev": { "bacon/bacon-qr-code": "^2.0", @@ -799,9 +849,9 @@ ], "support": { "issues": "https://github.com/antonioribeiro/google2fa-qrcode/issues", - "source": "https://github.com/antonioribeiro/google2fa-qrcode/tree/v3.0.0" + "source": "https://github.com/antonioribeiro/google2fa-qrcode/tree/v3.0.1" }, - "time": "2021-08-15T12:53:48+00:00" + "time": "2025-09-19T23:02:26+00:00" }, { "name": "psr/cache", @@ -1487,7 +1537,7 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -1543,7 +1593,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -1554,6 +1604,10 @@ "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" @@ -1563,16 +1617,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -1623,7 +1677,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -1634,12 +1688,16 @@ "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" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/service-contracts", @@ -1796,76 +1854,72 @@ } ], "time": "2024-09-25T14:11:13+00:00" - }, + } + ], + "packages-dev": [ { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, + "name": "phpstan/phpstan", + "version": "2.1.48", "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/231397213efb7c0a066ee024b5c3c87f2d3adfa0", + "reference": "231397213efb7c0a066ee024b5c3c87f2d3adfa0", "shasum": "" }, "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4|^8.0" }, "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "phpstan/phpstan-shim": "*" }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "files": [ + "bootstrap.php" + ] }, "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.", + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "assert", - "check", - "validate" + "dev", + "static analysis" ], "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, - "time": "2022-06-03T18:03:27+00:00" + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-04-15T20:24:19+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=7.4" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "7.4" }, diff --git a/core/ajax/cache.ajax.php b/core/ajax/cache.ajax.php index ee5b5baef7..bb7a56e802 100644 --- a/core/ajax/cache.ajax.php +++ b/core/ajax/cache.ajax.php @@ -28,7 +28,7 @@ if (init('action') == 'set') { unautorizedInDemo(); - cache::set(init('key'), init('value'), init('lifetime', 0), init('options', null)); + cache::set(init('key'), init('value'), init('lifetime', 0)); ajax::success(); } diff --git a/core/ajax/cmd.ajax.php b/core/ajax/cmd.ajax.php index 9b858a2dbc..6e1f905431 100644 --- a/core/ajax/cmd.ajax.php +++ b/core/ajax/cmd.ajax.php @@ -246,12 +246,12 @@ } if (init('action') == 'byEqLogic') { - if (init('typeCmd')) { - ajax::success(utils::o2a(cmd::byEqLogicId(init('eqLogic_id'), init('typeCmd')))); - } else { - ajax::success(utils::o2a(cmd::byEqLogicId(init('eqLogic_id')))); - } - } + if (init('typeCmd')) { + ajax::success(utils::o2a(cmd::byEqLogicId(init('eqLogic_id'), init('typeCmd')))); + } else { + ajax::success(utils::o2a(cmd::byEqLogicId(init('eqLogic_id')))); + } + } if (init('action') == 'getCmd') { $cmd = cmd::byId(init('id')); @@ -422,6 +422,13 @@ } $return['derive'] = $derive; $groupingType = init('groupingType'); + if (is_array($groupingType)) { + if (isset($groupingType['function']) && isset($groupingType['time'])) { + $groupingType = $groupingType['function'] . '::' . $groupingType['time']; + } else { + $groupingType = implode('::', array_values($groupingType)); + } + } if ($groupingType == '') { $groupingType = $cmd->getDisplay('groupingType'); } @@ -465,7 +472,7 @@ $data[] = $info_history; } } else { - $histories = history::getHistoryFromCalcul(jeedom::fromHumanReadable(init('id')), $dateStart, $dateEnd, init('allowZero', false), init('groupingType'), init('addFirstPreviousValue', false)); + $histories = history::getHistoryFromCalcul(jeedom::fromHumanReadable(init('id')), $dateStart, $dateEnd, init('allowZero', false), init('groupingType')); if (is_array($histories)) { foreach ($histories as $datetime => $value) { $info_history = array(); @@ -497,7 +504,7 @@ if (init('action') == 'getLastHistory') { $cmd = cmd::byId(init('id')); $_time = date('Y-m-d H:i:s', strtotime(init('time'))); - if(is_object($cmd)){ + if (is_object($cmd)) { ajax::success($cmd->getLastHistory($_time)); } else { throw new Exception(__('Nombre maximum de niveaux d’éléments affichés dans les graphiques de liens', __FILE__) . ' ' . init('id')); diff --git a/core/ajax/eqLogic.ajax.php b/core/ajax/eqLogic.ajax.php index 99f30677e3..b9479ebc2e 100644 --- a/core/ajax/eqLogic.ajax.php +++ b/core/ajax/eqLogic.ajax.php @@ -425,7 +425,7 @@ } if (count($cmdData['node']) > 0) { foreach ($cmdData['node'] as $name => $data) { - if (cmd::byId(str_replace('cmd', '', $data['id']))->getEqLogic_id() == $eqLogic->getId()) { + if (is_object(cmd::byId(str_replace('cmd', '', $data['id']))) && cmd::byId(str_replace('cmd', '', $data['id']))->getEqLogic_id() == $eqLogic->getId()) { continue; } diff --git a/core/ajax/interact.ajax.php b/core/ajax/interact.ajax.php index 31ad3f4008..cf022a81bf 100644 --- a/core/ajax/interact.ajax.php +++ b/core/ajax/interact.ajax.php @@ -30,7 +30,7 @@ $results = utils::o2a(interactDef::all()); foreach ($results as &$result) { $result['nbInteractQuery'] = count(interactQuery::byInteractDefId($result['id'])); - $result['nbEnableInteractQuery'] = count(interactQuery::byInteractDefId($result['id'], true)); + $result['nbEnableInteractQuery'] = count(interactQuery::byInteractDefId($result['id'])); if (isset($result['link_type']) && $result['link_type'] == 'cmd' && $result['link_id'] != '') { $link_id = ''; foreach (explode('&&', $result['link_id']) as $cmd_id) { @@ -49,7 +49,7 @@ if (init('action') == 'byId') { $result = utils::o2a(interactDef::byId(init('id'))); $result['nbInteractQuery'] = count(interactQuery::byInteractDefId($result['id'])); - $result['nbEnableInteractQuery'] = count(interactQuery::byInteractDefId($result['id'], true)); + $result['nbEnableInteractQuery'] = count(interactQuery::byInteractDefId($result['id'])); ajax::success(jeedom::toHumanReadable($result)); } diff --git a/core/ajax/listener.ajax.php b/core/ajax/listener.ajax.php index e0abe1d9d1..59144059eb 100644 --- a/core/ajax/listener.ajax.php +++ b/core/ajax/listener.ajax.php @@ -43,7 +43,7 @@ } if (init('action') == 'all') { - $listeners = utils::o2a(listener::all(true)); + $listeners = utils::o2a(listener::all()); foreach ($listeners as &$listener) { $listener['event_str'] = ''; foreach ($listener['event'] as $event) { diff --git a/core/ajax/object.ajax.php b/core/ajax/object.ajax.php index 43eac0e1f9..da8ec2bed6 100644 --- a/core/ajax/object.ajax.php +++ b/core/ajax/object.ajax.php @@ -231,7 +231,7 @@ function jeeAjax_objectToHtml($_id = -1, $_version = 'dashboard', $_category = ' if (!isConnect('admin')) { throw new Exception(__('401 - Accès non autorisé', __FILE__)); } - ajax::success(jeeObject::getUISelectList(init('none'), true)); + ajax::success(jeeObject::getUISelectList(init('none'))); } if (init('action') == 'getSummaryHtml') { diff --git a/core/ajax/plugin.ajax.php b/core/ajax/plugin.ajax.php index a6face2566..c5b95894d5 100644 --- a/core/ajax/plugin.ajax.php +++ b/core/ajax/plugin.ajax.php @@ -180,6 +180,21 @@ $infoPost .= ' - (' . ($daemon_info['last_launch'] ?? __('Inconnue', __FILE__)) . ')'; } + $healt = '[details="' . __('Santé', __FILE__) . '"]'; + $healt .= '```'; + foreach ((jeedom::health()) as $datas) { + if ($datas['state'] === 2) { + $healt .= "🟠 "; + } else if ($datas['state']) { + $healt .= "🟢 "; + } else { + $healt .= "🔴 "; + } + $healt .= $datas['name'] . ' : ' . str_replace(["\r","\n"], " ", $datas['result']) . ''; + } + $healt .= '```'; + $healt .= '[/details]'; + $infoPlugin = ''; if (method_exists($plugin_id, 'getConfigForCommunity')) { $infoPlugin .= '**' . __('Informations complémentaires', __FILE__) . '**'; @@ -190,7 +205,7 @@ $communitUrl = 'https://community.jeedom.com'; $ressource = '/new-topic?'; - $finalBody = br2nl($header . $infoPost . $footer . $infoPlugin); + $finalBody = br2nl($header . $infoPost . $footer . $healt . $infoPlugin); $data = array( 'category' => 'plugins/' . $plugin->getCategory(), diff --git a/core/ajax/queue.ajax.php b/core/ajax/queue.ajax.php index 3e094df417..e5957904d6 100644 --- a/core/ajax/queue.ajax.php +++ b/core/ajax/queue.ajax.php @@ -43,7 +43,7 @@ } if (init('action') == 'all') { - $queues = queue::all(true); + $queues = queue::all(); foreach ($queues as $queue) { $queue->refresh(); } diff --git a/core/ajax/report.ajax.php b/core/ajax/report.ajax.php index 178b0dc16d..9d3c7b0cd7 100644 --- a/core/ajax/report.ajax.php +++ b/core/ajax/report.ajax.php @@ -61,7 +61,7 @@ foreach (ls($path, '*') as $value) { unlink($path . $value); } - ajax::success($return); + ajax::success(); } throw new Exception(__('Aucune méthode correspondante à :', __FILE__) . ' ' . init('action')); diff --git a/core/ajax/user.ajax.php b/core/ajax/user.ajax.php index 8cb6103efb..d4ebf911a0 100644 --- a/core/ajax/user.ajax.php +++ b/core/ajax/user.ajax.php @@ -52,6 +52,7 @@ throw new Exception(__('Double authentification requise', __FILE__), -32012); } if (!login(init('username'), init('password'), init('twoFactorCode'))) { + log::add('connection', 'info',network::getClientIp().' - '. __('Mot de passe ou nom d\'utilisateur incorrect', __FILE__)); throw new Exception(__('Mot de passe ou nom d\'utilisateur incorrect', __FILE__)); } } @@ -78,6 +79,7 @@ $_SESSION['user']->save(); @session_write_close(); } + log::add('connection', 'info',network::getClientIp().' - '. __('Connexion réussie pour : ', __FILE__).$_SESSION['user']->getLogin()); ajax::success(); } @@ -343,7 +345,8 @@ if (init('action') == 'removeBanIp') { unautorizedInDemo(); - ajax::success(user::removeBanIp()); + user::removeBanIp(); + ajax::success(); } if (init('action') == 'supportAccess') { diff --git a/core/ajax/view.ajax.php b/core/ajax/view.ajax.php index 033976e1de..23eff4530d 100644 --- a/core/ajax/view.ajax.php +++ b/core/ajax/view.ajax.php @@ -164,12 +164,20 @@ } unautorizedInDemo(); $components = json_decode(init('components'), true); - $sql = ''; + $allowedTypes = array('cmd', 'eqLogic', 'scenario'); foreach ($components as $component) { if (!isset($component['viewZone_id']) || !is_numeric($component['viewZone_id']) || !is_numeric($component['id']) || !is_numeric($component['viewOrder']) || (isset($component['object_id']) && !is_numeric($component['object_id']))) { continue; } - $sql .= 'UPDATE viewData SET `order`= ' . $component['viewOrder'] . ' WHERE link_id=' . $component['id'] . ' AND type="' . $component['type'] . '" AND viewZone_id=' . $component['viewZone_id'] . ';'; + if (!isset($component['type']) || !in_array($component['type'], $allowedTypes)) { + continue; + } + DB::Prepare('UPDATE viewData SET `order`= :viewOrder WHERE link_id= :id AND type= :type AND viewZone_id= :viewZone_id', array( + 'viewOrder' => $component['viewOrder'], + 'id' => $component['id'], + 'type' => $component['type'], + 'viewZone_id' => $component['viewZone_id'], + ), DB::FETCH_TYPE_ROW); if ($component['type'] == 'eqLogic') { unset($component['type']); $eqLogic = eqLogic::byId($component['id']); @@ -188,9 +196,6 @@ $scenario->save(true); } } - if ($sql != '') { - DB::Prepare($sql, array(), DB::FETCH_TYPE_ROW); - } ajax::success(); } diff --git a/core/api/jeeApi.php b/core/api/jeeApi.php index 87c2a76b37..5bb0adeec1 100644 --- a/core/api/jeeApi.php +++ b/core/api/jeeApi.php @@ -416,7 +416,7 @@ if (is_object($_USER_GLOBAL) && $_USER_GLOBAL->getProfils() != 'admin') { throw new Exception(__('Vous n\'êtes pas autorisé à effectuer cette action', __FILE__) . ' ' . $jsonrpc->getMethod(), -32001); } - jeedom::update($params['options'], 0); + jeedom::update($params['options']); $jsonrpc->makeSuccess('ok'); } @@ -1276,7 +1276,7 @@ throw new Exception(__('Vous n\'avez pas les droits de faire cette action', __FILE__), -32701); } unautorizedInDemo(); - jeedom::update('', 0); + jeedom::update(''); $jsonrpc->makeSuccess('ok'); } diff --git a/core/api/proApi.php b/core/api/proApi.php index cdf7f18ac9..1e9b781402 100644 --- a/core/api/proApi.php +++ b/core/api/proApi.php @@ -147,7 +147,7 @@ $result = jeedom::version(); $advice = ''; $productName = config::byKey('product_name'); - $health[] = array('plugin' => 'core', 'type' => 'Version '. $productName , 'defaut' => $defaut, 'result' => $result, 'advice' => $advice); + $health[] = array('plugin' => 'core', 'type' => 'Version ' . $productName, 'defaut' => $defaut, 'result' => $result, 'advice' => $advice); $defaut = 0; $result = phpversion(); @@ -199,7 +199,7 @@ $result = 'OK'; } else { $filename = __DIR__ . '/../../cache.tar.gz'; - $result = 'OK (' . date('Y-m-d H:i:s',(int) filemtime($filename)) . ')'; + $result = 'OK (' . date('Y-m-d H:i:s', (int) filemtime($filename)) . ')'; } } else { $result = 'NOK'; @@ -569,8 +569,8 @@ $jsonrpc->makeSuccess($scenario->stop()); } if ($params['state'] == 'run') { - $scenario->addTag('trigger','api'); - $scenario->addTag('trigger_message',__('Scénario exécuté sur appel API', __FILE__)); + $scenario->addTag('trigger', 'api'); + $scenario->addTag('trigger_message', __('Scénario exécuté sur appel API', __FILE__)); $jsonrpc->makeSuccess($scenario->launch()); } if ($params['state'] == 'enable') { @@ -584,147 +584,18 @@ throw new Exception(__('Le paramètre "state" ne peut être vide et doit avoir pour valeur [run,stop,enable,disable]', __FILE__)); } - /* * ************************JeeNetwork*************************** */ - if ($jsonrpc->getMethod() == 'jeeNetwork::handshake') { - if (config::byKey('jeeNetwork::mode') != 'slave') { - throw new Exception(__('Impossible d\'ajouter une box Jeedom non esclave à un réseau Jeedom', __FILE__)); - } - $auiKey = config::byKey('auiKey'); - if ($auiKey == '') { - $auiKey = config::genKey(255); - config::save('auiKey', $auiKey); - } - $return = array( - 'mode' => config::byKey('jeeNetwork::mode'), - 'nbUpdate' => update::nbNeedUpdate(), - 'version' => jeedom::version(), - 'nbMessage' => message::nbMessage(), - 'auiKey' => $auiKey, - 'jeedom::url' => config::byKey('jeedom::url'), - 'ngrok::port' => config::byKey('ngrok::port'), - ); - if (!filter_var(network::getNetworkAccess('external', 'ip'), FILTER_VALIDATE_IP) && network::getNetworkAccess('external', 'ip') != '') { - $return['jeedom::url'] = network::getNetworkAccess('internal'); - } - foreach (plugin::listPlugin(true) as $plugin) { - if ($plugin->getAllowRemote() == 1) { - $return['plugin'][] = $plugin->getId(); - } - } - $address = (isset($params['address']) && $params['address'] != '') ? $params['address'] : getClientIp(); - config::save('jeeNetwork::master::ip', $address); - config::save('jeeNetwork::master::apikey', $params['apikey_master']); - config::save('jeeNetwork::slave::id', $params['slave_id']); - if (config::byKey('internalAddr') == '') { - config::save('internalAddr', $params['slave_ip']); - } - jeeNetwork::testMaster(); - $jsonrpc->makeSuccess($return); - } + /* * ************************System*************************** */ - if ($jsonrpc->getMethod() == 'jeeNetwork::reload') { - foreach (plugin::listPlugin(true) as $plugin) { - try { - $plugin->launch('slaveReload'); - } catch (Exception $ex) { - } - } - $jsonrpc->makeSuccess('ok'); - } - - if ($jsonrpc->getMethod() == 'jeeNetwork::halt') { + if ($jsonrpc->getMethod() == 'jeedom::halt') { jeedom::haltSystem(); $jsonrpc->makeSuccess('ok'); } - if ($jsonrpc->getMethod() == 'jeeNetwork::reboot') { + if ($jsonrpc->getMethod() == 'jeedom::reboot') { jeedom::rebootSystem(); $jsonrpc->makeSuccess('ok'); } - if ($jsonrpc->getMethod() == 'jeeNetwork::update') { - jeedom::update('', 0); - $jsonrpc->makeSuccess('ok'); - } - - if ($jsonrpc->getMethod() == 'jeeNetwork::checkUpdate') { - update::checkAllUpdate(); - $jsonrpc->makeSuccess('ok'); - } - - if ($jsonrpc->getMethod() == 'jeeNetwork::receivedBackup') { - if (config::byKey('jeeNetwork::mode') == 'slave') { - throw new Exception(__('Seul un maître peut recevoir une sauvegarde', __FILE__)); - } - $jeeNetwork = jeeNetwork::byId($params['slave_id']); - if (!is_object($jeeNetwork)) { - throw new Exception(__('Aucun esclave correspondant à l\'ID :', __FILE__) . ' ' . secureXSS($params['slave_id'])); - } - if (substr(config::byKey('backup::path'), 0, 1) != '/') { - $backup_dir = __DIR__ . '/../../' . config::byKey('backup::path'); - } else { - $backup_dir = config::byKey('backup::path'); - } - $uploaddir = $backup_dir . '/slave/'; - if (!file_exists($uploaddir)) { - mkdir($uploaddir); - } - if (!file_exists($uploaddir)) { - throw new Exception(__('Répertoire de téléversement non trouvé :', __FILE__) . ' ' . secureXSS($uploaddir)); - } - $_file = $_FILES['file']; - $extension = strtolower(strrchr($_file['name'], '.')); - if (!in_array($extension, array('.tar.gz', '.gz', '.tar'))) { - throw new Exception(__('Extension du fichier non valide (autorisé .tar.gz, .tar et .gz) :', __FILE__) . ' ' . secureXSS($extension)); - } - if (filesize($_file['tmp_name']) > 50000000) { - throw new Exception(__('La taille du fichier est trop importante (maximum 50Mo)', __FILE__)); - } - $uploadfile = $uploaddir . $jeeNetwork->getId() . '-' . $jeeNetwork->getName() . '-' . $jeeNetwork->getConfiguration('version') . '-' . date('Y-m-d_H\hi') . '.tar' . $extension; - if (!move_uploaded_file($_file['tmp_name'], $uploadfile)) { - throw new Exception(__('Impossible de téléverser le fichier', __FILE__)); - } - system('find ' . $uploaddir . $jeeNetwork->getId() . '*' . ' -mtime +' . config::byKey('backup::keepDays') . ' -print | xargs -r rm'); - $jsonrpc->makeSuccess('ok'); - } - - if ($jsonrpc->getMethod() == 'jeeNetwork::restoreBackup') { - if (config::byKey('jeeNetwork::mode') != 'slave') { - throw new Exception(__('Seul un esclave peut restaurer une sauvegarde', __FILE__)); - } - if (substr(config::byKey('backup::path'), 0, 1) != '/') { - $uploaddir = __DIR__ . '/../../' . config::byKey('backup::path'); - } else { - $uploaddir = config::byKey('backup::path'); - } - if (!file_exists($uploaddir)) { - mkdir($uploaddir); - } - if (!file_exists($uploaddir)) { - throw new Exception(__('Répertoire de téléversement non trouvé :', __FILE__) . ' ' . secureXSS($uploaddir)); - } - $_file = $_FILES['file']; - $extension = strtolower(strrchr($_file['name'], '.')); - if (!in_array($extension, array('.tar.gz', '.gz', '.tar'))) { - throw new Exception(__('Extension du fichier non valide (autorisé .tar.gz, .tar et .gz) :', __FILE__) . ' ' . secureXSS($extension)); - } - if (filesize($_file['tmp_name']) > 50000000) { - throw new Exception(__('La taille du fichier est trop importante (maximum 50Mo)', __FILE__)); - } - $backup_name = 'backup-' . jeedom::version() . '-' . date("d-m-Y-H\hi") . '.tar.gz'; - $uploadfile = $uploaddir . '/' . $backup_name; - if (!move_uploaded_file($_file['tmp_name'], $uploadfile)) { - throw new Exception(__('Impossible de téléverser le fichier', __FILE__)); - } - jeedom::restore($uploadfile, true); - $jsonrpc->makeSuccess('ok'); - } - - if ($jsonrpc->getMethod() == 'jeeNetwork::backup') { - jeedom::backup(true); - $jsonrpc->makeSuccess('ok'); - } - /* * ************************Backup*************************** */ if ($jsonrpc->getMethod() == 'backup::list') { @@ -751,7 +622,7 @@ } if ($jsonrpc->getMethod() == 'backup::restoreMarket') { - repo_market::backup_restore($params['backup'], true); + repo_market::backup_restore($params['backup']); $jsonrpc->makeSuccess(); } @@ -814,9 +685,9 @@ /* * ************************Plugin*************************** */ if ($jsonrpc->getMethod() == 'plugin::install') { try { - $market = market::byId($params['plugin_id']); + $market = repo_market::byId($params['plugin_id']); } catch (Exception $e) { - $market = market::byLogicalId($params['plugin_id']); + $market = repo_market::byLogicalId($params['plugin_id']); } if (!is_object($market)) { throw new Exception(__('Impossible de trouver l\'objet associé :', __FILE__) . ' ' . secureXSS($params['plugin_id'])); @@ -829,7 +700,7 @@ } if ($jsonrpc->getMethod() == 'plugin::remove') { - $market = market::byId($params['plugin_id']); + $market = repo_market::byId($params['plugin_id']); if (!is_object($market)) { throw new Exception(__('Impossible de trouver l\'objet associé :', __FILE__) . ' ' . secureXSS($params['plugin_id'])); } @@ -857,7 +728,7 @@ } if ($jsonrpc->getMethod() == 'update::update') { - jeedom::update('', 0); + jeedom::update(''); $jsonrpc->makeSuccess('ok'); } diff --git a/core/class/cache.class.php b/core/class/cache.class.php index 277a5293a4..88ca33f7be 100644 --- a/core/class/cache.class.php +++ b/core/class/cache.class.php @@ -30,17 +30,17 @@ class cache { /* * ***********************Methode static*************************** */ - public static function getEngine(){ - if(self::$_engine != null){ + public static function getEngine() { + if (self::$_engine != null) { return self::$_engine; } self::$_engine = config::byKey('cache::engine'); - if(!class_exists(self::$_engine)){ - config::save('cache::engine','FileCache'); + if (!class_exists(self::$_engine)) { + config::save('cache::engine', 'FileCache'); self::$_engine = 'FileCache'; } - if(method_exists(self::$_engine,'isOk') && !self::$_engine::isOk()){ - config::save('cache::engine','FileCache'); + if (method_exists(self::$_engine, 'isOk') && !self::$_engine::isOk()) { + config::save('cache::engine', 'FileCache'); self::$_engine = 'FileCache'; } return self::$_engine; @@ -51,7 +51,7 @@ public static function set($_key, $_value, $_lifetime = 0) { ->setKey($_key) ->setValue($_value) ->setLifetime($_lifetime) - ->save(); + ->save(); } public static function delete($_key) { @@ -76,7 +76,7 @@ public static function byKey($_key) { return $cache; } - public static function exist($_key){ + public static function exist($_key) { return (self::byKey($_key)->getValue(null) !== null); } @@ -85,31 +85,31 @@ public static function flush() { } public static function persist() { - if(method_exists(self::getEngine(),'persist')){ + if (method_exists(self::getEngine(), 'persist')) { self::getEngine()::persist(); } } public static function isPersistOk(): bool { - if(method_exists(self::getEngine(),'isPersistOk')){ + if (method_exists(self::getEngine(), 'isPersistOk')) { return self::getEngine()::isPersistOk(); } return true; } public static function restore() { - if(method_exists(self::getEngine(),'restore')){ + if (method_exists(self::getEngine(), 'restore')) { self::getEngine()::restore(); } } public static function clean() { - if(method_exists(self::getEngine(),'clean')){ + if (method_exists(self::getEngine(), 'clean')) { self::getEngine()::clean(); } $caches = self::getEngine()::all(); foreach ($caches as $cache) { - if(!is_object($cache)){ + if (!is_object($cache)) { continue; } $matches = null; @@ -188,7 +188,7 @@ public function setLifetime($_lifetime): self { } public function getDatetime() { - return date('Y-m-d H:i:s',(int) $this->timestamp); + return date('Y-m-d H:i:s', (int) $this->timestamp); } public function setDatetime($_datetime): self { @@ -196,11 +196,11 @@ public function setDatetime($_datetime): self { return $this; } - public function getTimestamp(){ + public function getTimestamp() { return $this->timestamp; } - public function setTimestamp($_timestamp){ + public function setTimestamp($_timestamp) { $this->timestamp = $_timestamp; return $this; } @@ -209,73 +209,72 @@ public function setTimestamp($_timestamp){ class MariadbCache { - public static function all(){ + public static function all() { $sql = 'SELECT `key`,`timestamp`,`value`,`lifetime` FROM cache'; - $results = DB::Prepare($sql,array(), DB::FETCH_TYPE_ALL, PDO::FETCH_CLASS,'cache'); + $results = DB::Prepare($sql, array(), DB::FETCH_TYPE_ALL, PDO::FETCH_CLASS, 'cache'); foreach ($results as $cache) { $cache->setValue(unserialize($cache->getValue())); } return $results; } - public static function clean(){ - $sql = 'DELETE + public static function clean() { + $sql = 'DELETE FROM cache WHERE `lifetime` > 0 AND (`timestamp`+`lifetime`) < UNIX_TIMESTAMP()'; - return DB::Prepare($sql,array(), DB::FETCH_TYPE_ROW, PDO::FETCH_CLASS); + return DB::Prepare($sql, array(), DB::FETCH_TYPE_ROW, PDO::FETCH_CLASS); } - public static function fetch($_key){ + public static function fetch($_key) { $sql = 'SELECT `key`,`timestamp`,`value`,`lifetime` FROM cache WHERE `key`=:key'; - $cache = DB::Prepare($sql,array('key' => $_key), DB::FETCH_TYPE_ROW, PDO::FETCH_CLASS,'cache'); - if($cache === false || !is_object($cache)){ + $cache = DB::Prepare($sql, array('key' => $_key), DB::FETCH_TYPE_ROW, PDO::FETCH_CLASS, 'cache'); + if ($cache === false || !is_object($cache)) { return null; } - if($cache->getLifetime() > 0 && ($cache->getTimestamp() + $cache->getLifetime()) < strtotime('now')){ + if ($cache->getLifetime() > 0 && ($cache->getTimestamp() + $cache->getLifetime()) < strtotime('now')) { return null; } $cache->setValue(unserialize($cache->getValue())); return $cache; } - public static function delete($_key){ - $sql = 'DELETE + public static function delete($_key) { + $sql = 'DELETE FROM cache WHERE `key`=:key'; - return DB::Prepare($sql,array('key' => $_key), DB::FETCH_TYPE_ROW); + return DB::Prepare($sql, array('key' => $_key), DB::FETCH_TYPE_ROW); } - public static function deleteAll(){ - return DB::Prepare('TRUNCATE cache',array(), DB::FETCH_TYPE_ROW); + public static function deleteAll() { + return DB::Prepare('TRUNCATE cache', array(), DB::FETCH_TYPE_ROW); } - public static function save($_cache){ + public static function save($_cache) { $value = array( 'key' => $_cache->getKey(), 'value' => serialize($_cache->getValue()), - 'lifetime' =>$_cache->getLifetime(), + 'lifetime' => $_cache->getLifetime(), 'timestamp' => $_cache->getTimestamp() ); $sql = 'REPLACE INTO cache SET `key`=:key, `value`=:value,`timestamp`=:timestamp,`lifetime`=:lifetime'; - return DB::Prepare($sql,$value, DB::FETCH_TYPE_ROW); + return DB::Prepare($sql, $value, DB::FETCH_TYPE_ROW); } - } class RedisCache { private static $connection = null; - public static function isOk(){ + public static function isOk() { return class_exists('redis'); } - public static function getConnection(){ - if(static::$connection !== null){ + public static function getConnection() { + if (static::$connection !== null) { return static::$connection; } $redis = new Redis(); @@ -284,7 +283,7 @@ public static function getConnection(){ return static::$connection; } - public static function all(){ + public static function all() { $return = array(); $keys = self::getConnection()->keys('*'); foreach ($keys as $key) { @@ -293,88 +292,87 @@ public static function all(){ return $return; } - public static function fetch($_key){ + public static function fetch($_key) { $data = self::getConnection()->get($_key); - if($data === false){ + if ($data === false) { return null; } return @unserialize($data); } - public static function delete($_key){ + public static function delete($_key) { self::getConnection()->del($_key); } - public static function deleteAll(){ + public static function deleteAll() { return self::getConnection()->flushDb(); } - public static function save($_cache){ - if($_cache->getLifetime() > 0){ - self::getConnection()->set($_cache->getKey(),serialize($_cache), $_cache->getLifetime()); - }else{ - self::getConnection()->set($_cache->getKey(),serialize($_cache)); + public static function save($_cache) { + if ($_cache->getLifetime() > 0) { + self::getConnection()->set($_cache->getKey(), serialize($_cache), $_cache->getLifetime()); + } else { + self::getConnection()->set($_cache->getKey(), serialize($_cache)); } } - } class FileCache { - public static function all(){ + public static function all() { $return = array(); - foreach (ls(jeedom::getTmpFolder('cache'), '*',false,array('files')) as $file) { + foreach (ls(jeedom::getTmpFolder('cache'), '*', false, array('files')) as $file) { $return[] = self::fetch(base64_decode($file)); } return $return; } - public static function clean(){ - foreach (ls(jeedom::getTmpFolder('cache'), '*',false,array('files')) as $file) { - $cache = unserialize(file_get_contents(jeedom::getTmpFolder('cache').'/'.$file)); - if(!is_object($cache)){ - unlink(jeedom::getTmpFolder('cache').'/'.$file); + public static function clean() { + foreach (ls(jeedom::getTmpFolder('cache'), '*', false, array('files')) as $file) { + $cache = unserialize(file_get_contents(jeedom::getTmpFolder('cache') . '/' . $file)); + if (!is_object($cache)) { + unlink(jeedom::getTmpFolder('cache') . '/' . $file); continue; } - if($cache->getLifetime() > 0 && ($cache->getTimestamp() + $cache->getLifetime()) < strtotime('now')){ - unlink(jeedom::getTmpFolder('cache').'/'.$file); + if ($cache->getLifetime() > 0 && ($cache->getTimestamp() + $cache->getLifetime()) < strtotime('now')) { + unlink(jeedom::getTmpFolder('cache') . '/' . $file); } } } - public static function fetch($_key){ - $data = @file_get_contents(jeedom::getTmpFolder('cache').'/'.base64_encode($_key)); - if($data === false){ - return null; - } - $cache = unserialize($data); - if(!is_object($cache)){ + public static function fetch($_key) { + $data = @file_get_contents(jeedom::getTmpFolder('cache') . '/' . base64_encode($_key)); + if ($data === false) { return null; } - if($cache->getLifetime() > 0 && ($cache->getTimestamp() + $cache->getLifetime()) < strtotime('now')){ + $cache = unserialize($data); + if (!is_object($cache)) { + return null; + } + if ($cache->getLifetime() > 0 && ($cache->getTimestamp() + $cache->getLifetime()) < strtotime('now')) { self::delete($_key); return null; } return $cache; } - public static function delete($_key){ - @unlink(jeedom::getTmpFolder('cache').'/'.base64_encode($_key)); + public static function delete($_key) { + @unlink(jeedom::getTmpFolder('cache') . '/' . base64_encode($_key)); } - public static function deleteAll(){ - return shell_exec(system::getCmdSudo().' rm -rf '.jeedom::getTmpFolder('cache')); + public static function deleteAll() { + return shell_exec(system::getCmdSudo() . ' rm -rf ' . jeedom::getTmpFolder('cache')); } - public static function save($_cache){ - file_put_contents(jeedom::getTmpFolder('cache').'/'.base64_encode($_cache->getKey()),serialize($_cache)); + public static function save($_cache) { + file_put_contents(jeedom::getTmpFolder('cache') . '/' . base64_encode($_cache->getKey()), serialize($_cache)); } public static function persist() { $cache_dir = jeedom::getTmpFolder('cache'); try { $cmd = system::getCmdSudo() . 'rm -rf ' . __DIR__ . '/../../cache.tar.gz;cd ' . $cache_dir . ';'; - $cmd .= system::getCmdSudo() . 'tar cfz ' . __DIR__ . '/../../cache.tar.gz * 2>&1 > /dev/null;'; + $cmd .= system::getCmdSudo() . 'tar cfz ' . __DIR__ . '/../../cache.tar.gz . 2>&1 > /dev/null;'; $cmd .= system::getCmdSudo() . 'chmod 774 ' . __DIR__ . '/../../cache.tar.gz;'; $cmd .= system::getCmdSudo() . 'chown ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . __DIR__ . '/../../cache.tar.gz;'; $cmd .= system::getCmdSudo() . 'chown -R ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . $cache_dir . ';'; @@ -410,5 +408,4 @@ public static function restore() { $cmd .= 'chmod -R 777 ' . $cache_dir . ' 2>&1 > /dev/null;'; com_shell::execute($cmd); } - } diff --git a/core/class/cmd.class.php b/core/class/cmd.class.php index 7c0ffb79b5..f785ef0c8b 100644 --- a/core/class/cmd.class.php +++ b/core/class/cmd.class.php @@ -1042,10 +1042,10 @@ public function formatValue($_value, $_quote = false) { return intval($binary xor boolval($this->getConfiguration('invertBinary', false))); case 'numeric': if ($this->getConfiguration('historizeRound') !== '' && is_numeric($this->getConfiguration('historizeRound')) && $this->getConfiguration('historizeRound') >= 0) { - if (!is_numeric($_value)) { - log::add('cmd', 'error', __('La formule de calcul doit retourner une valeur numérique uniquement : ', __FILE__) . $this->getHumanName() . ' => ' . $_value); - $_value = (float) (str_replace(',', '.', $_value)); - } + if (!is_numeric($_value)) { + log::add('cmd', 'error', __('La formule de calcul doit retourner une valeur numérique uniquement : ', __FILE__) . $this->getHumanName() . ' => ' . $_value); + $_value = (float) (str_replace(',', '.', $_value)); + } $_value = round($_value, $this->getConfiguration('historizeRound')); } if ($_value > $this->getConfiguration('maxValue', $_value)) { @@ -1757,13 +1757,13 @@ public function toHtml($_version = 'dashboard', $_options = '') { $replace['#hide_history#'] = ''; $historyStatistique = $this->getStatistique($startHist, date('Y-m-d H:i:s')); if ($historyStatistique['avg'] == 0 && $historyStatistique['min'] == 0 && $historyStatistique['max'] == 0) { - $replace['#averageHistoryValue#'] = round(intval($replace['#state#']), 1); - $replace['#minHistoryValue#'] = round(intval($replace['#state#']), 1); - $replace['#maxHistoryValue#'] = round(intval($replace['#state#']), 1); + $replace['#averageHistoryValue#'] = round(floatval($replace['#state#']), 1); + $replace['#minHistoryValue#'] = round(floatval($replace['#state#']), 1); + $replace['#maxHistoryValue#'] = round(floatval($replace['#state#']), 1); } else { - $replace['#averageHistoryValue#'] = round(intval($historyStatistique['avg']), 1); - $replace['#minHistoryValue#'] = round(intval($historyStatistique['min']), 1); - $replace['#maxHistoryValue#'] = round(intval($historyStatistique['max']), 1); + $replace['#averageHistoryValue#'] = round(floatval($historyStatistique['avg']), 1); + $replace['#minHistoryValue#'] = round(floatval($historyStatistique['min']), 1); + $replace['#maxHistoryValue#'] = round(floatval($historyStatistique['max']), 1); } $startHist = date('Y-m-d H:i:s', strtotime(date('Y-m-d H:i:s') . ' -' . config::byKey('historyCalculTendance') . ' hour')); $tendance = $this->getTendance($startHist, date('Y-m-d H:i:s')); @@ -2330,7 +2330,7 @@ public function pushInflux($_value = null) { return; } - public function dropInfluxDatabase() { + public static function dropInfluxDatabase() { try { $database = cmd::getInflux(); if ($database == '') { @@ -3071,7 +3071,7 @@ public function getDisplay($_key = '', $_default = '') { } public function setDisplay($_key, $_value) { - if ($this->getDisplay($_key) !== $_value) { + if ($this->getDisplay($_key, null) !== $_value) { $this->_needRefreshWidget = true; $this->_changed = true; } diff --git a/core/class/config.class.php b/core/class/config.class.php index a33f7d2832..8f46a09f84 100644 --- a/core/class/config.class.php +++ b/core/class/config.class.php @@ -29,26 +29,44 @@ class config { /* * ***********************Methode static*************************** */ - public static function getDefaultConfiguration(string $_plugin = 'core') { - if (!isset(self::$defaultConfiguration[$_plugin])) { - if ($_plugin == 'core') { - self::$defaultConfiguration[$_plugin] = parse_ini_file(__DIR__ . '/../../core/config/default.config.ini', true); - if (file_exists(__DIR__ . '/../../data/custom/custom.config.ini')) { - $custom = parse_ini_file(__DIR__ . '/../../data/custom/custom.config.ini', true); - self::$defaultConfiguration[$_plugin]['core'] = array_merge(self::$defaultConfiguration[$_plugin]['core'], $custom['core']); - } - } else { - $filename = __DIR__ . '/../../plugins/' . $_plugin . '/core/config/' . $_plugin . '.config.ini'; - if (file_exists($filename)) { - self::$defaultConfiguration[$_plugin] = parse_ini_file($filename, true); - } - } + /** + * Get default configuration for core or plugin + * @param string $_plugin + * @return array + */ + public static function getDefaultConfiguration(string $_plugin = 'core'): array { + if (isset(self::$defaultConfiguration[$_plugin])) { + return self::$defaultConfiguration[$_plugin]; } - if (!isset(self::$defaultConfiguration[$_plugin])) { + + if ($_plugin == 'core') { + self::$defaultConfiguration[$_plugin] = parse_ini_file(__DIR__ . '/../../core/config/default.config.ini', true); + + if (file_exists(__DIR__ . '/../../data/custom/custom.config.ini')) { + $custom = parse_ini_file(__DIR__ . '/../../data/custom/custom.config.ini', true); + self::$defaultConfiguration[$_plugin]['core'] = array_merge(self::$defaultConfiguration[$_plugin]['core'], $custom['core']); + } + } else { self::$defaultConfiguration[$_plugin] = array(); + + $filename = __DIR__ . '/../../plugins/' . $_plugin . '/core/config/' . $_plugin . '.config.ini'; + if (file_exists($filename)) { + self::$defaultConfiguration[$_plugin] = parse_ini_file($filename, true); + } + + $specificConf = __DIR__ . '/../../plugins/' . $_plugin . '/core/config/specific.config.ini'; + if (file_exists($specificConf)) { + $specific = parse_ini_file($specificConf, true); + $hardware = strtolower(jeedom::getHardwareName()); + if (isset($specific[$hardware])) { + self::$defaultConfiguration[$_plugin] = array_replace_recursive(self::$defaultConfiguration[$_plugin], $specific[$hardware]); + } + } } + return self::$defaultConfiguration[$_plugin]; } + /** * Save key to config * @param string $_key @@ -111,8 +129,8 @@ public static function save($_key, $_value, $_plugin = 'core') { /** * Delete key from config - * @param string $_key nom de la clef à supprimer - * @return boolean vrai si ok faux sinon + * @param string $_key + * @return boolean */ public static function remove(string $_key, string $_plugin = 'core') { if ($_key == "*" && $_plugin != 'core') { @@ -122,11 +140,11 @@ public static function remove(string $_key, string $_plugin = 'core') { $sql = 'DELETE FROM config WHERE plugin=:plugin'; DB::Prepare($sql, $values, DB::FETCH_TYPE_ROW); - foreach (self::$cache as $cacheKey => $value) { - if (strpos($cacheKey, $_plugin . '::') === 0) { - unset(self::$cache[$cacheKey]); - } - } + foreach (self::$cache as $cacheKey => $value) { + if (strpos($cacheKey, $_plugin . '::') === 0) { + unset(self::$cache[$cacheKey]); + } + } } else { $values = array( 'plugin' => $_plugin, @@ -136,15 +154,15 @@ public static function remove(string $_key, string $_plugin = 'core') { WHERE `key`=:key AND plugin=:plugin'; DB::Prepare($sql, $values, DB::FETCH_TYPE_ROW); - unset(self::$cache[$_plugin . '::' . $_key]); + unset(self::$cache[$_plugin . '::' . $_key]); } return true; } /** * Get config by key - * @param string $_key nom de la clef dont on veut la valeur - * @return string valeur de la clef + * @param string $_key + * @return string */ public static function byKey($_key, $_plugin = 'core', $_default = '', $_forceFresh = false) { if (!$_forceFresh && isset(self::$cache[$_plugin . '::' . $_key]) && !in_array($_key, self::$nocache)) { @@ -227,6 +245,26 @@ public static function byKeys($_keys, $_plugin = 'core', $_default = '') { return $return; } + /** + * Get list of plugins|keys from unencrypted value + * @param mixed $_value + * @param string $_key (optional) + * @return array + */ + public static function byValue($_value, string $_key = null): array { + $values = array( + 'value' => $_value + ); + + if ($_key) { + $values['key'] = $_key; + $sql = 'SELECT `plugin` FROM config WHERE `value`=:value AND `key`=:key'; + } else { + $sql = 'SELECT `plugin`,`key` FROM config WHERE `value`=:value'; + } + return DB::Prepare($sql, $values, DB::FETCH_TYPE_ALL); + } + public static function searchKey($_key, $_plugin = 'core') { $values = array( 'plugin' => $_plugin, @@ -251,9 +289,9 @@ public static function searchKey($_key, $_plugin = 'core') { } public static function genKey($_car = 64) { - if ($_car > 256) { - throw new \Exception('Key length too long'); - } + if ($_car > 256) { + throw new \Exception('Key length too long'); + } $key = ''; $chaine = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for ($i = 0; $i < $_car; $i++) { @@ -325,7 +363,11 @@ public static function getGenericTypes($_coreOnly = false) { } } } - asort($types['byFamily'], SORT_STRING | SORT_FLAG_CASE); + + if (is_array($types['byFamily'])) { + asort($types['byFamily'], SORT_STRING | SORT_FLAG_CASE); + } + return $types; } diff --git a/core/class/history.class.php b/core/class/history.class.php index 9c9e4dcd6b..3b4c0d4203 100644 --- a/core/class/history.class.php +++ b/core/class/history.class.php @@ -226,10 +226,6 @@ public static function archive() { DB::Prepare($sql, array()); $sql = 'DELETE FROM historyArch WHERE `value` IS NULL'; DB::Prepare($sql, array()); - $sql = 'DELETE FROM history WHERE `value` IS NULL'; - DB::Prepare($sql, array()); - $sql = 'DELETE FROM historyArch WHERE `value` IS NULL'; - DB::Prepare($sql, array()); if (config::byKey('historyArchivePackage') < 1) { config::save('historyArchivePackage', 1); } diff --git a/core/class/interactQuery.class.php b/core/class/interactQuery.class.php index be5117a895..341120a283 100644 --- a/core/class/interactQuery.class.php +++ b/core/class/interactQuery.class.php @@ -667,7 +667,7 @@ public static function contextualReply($_query, $_parameters = array(), $_lastCm return $return; } - public function replaceForContextual($_replace, $_by, $_in) { + public static function replaceForContextual($_replace, $_by, $_in) { return str_replace(strtolower(sanitizeAccent($_replace)), strtolower(sanitizeAccent($_by)), str_replace($_replace, $_by, $_in)); } diff --git a/core/class/jeedom.class.php b/core/class/jeedom.class.php index d090860d81..92a47f48ec 100644 --- a/core/class/jeedom.class.php +++ b/core/class/jeedom.class.php @@ -222,7 +222,7 @@ public static function health() { $state = self::isDateOk(); $cache = cache::byKey('hour'); $lastKnowDate = $cache->getValue(); - if($lastKnowDate === ""){ + if ($lastKnowDate === "") { $lastKnowDate = 0; } $return[] = array( @@ -294,17 +294,20 @@ public static function health() { $state = false; } else { $version = trim(strtolower(file_get_contents('/etc/debian_version'))); - if (version_compare($version, '8', '<')) { - if (strpos($version, 'jessie') === false && strpos($version, 'stretch') === false) { + $majorVersion = intval($version); + if ($majorVersion > 0) { + if ($majorVersion < 11 || $majorVersion > 12) { $state = false; } + } else if (strpos($version, 'bullseye') === false && strpos($version, 'bookworm') === false) { + $state = false; } } $return[] = array( 'name' => __('Version OS', __FILE__), 'state' => $state, 'result' => ($state) ? $uname . ' [' . $version . ']' : $uname, - 'comment' => ($state) ? '' : __('Vous n\'êtes pas sur un OS officiellement supporté par l\'équipe Jeedom (toute demande de support pourra donc être refusée). Les OS officiellement supportés sont Debian Strech et Debian Buster', __FILE__), + 'comment' => ($state) ? '' : __("Cet OS n'est pas pris en charge, toute demande de support pourra donc être refusée (voir la documentation sur la compatibilité logicielle).", __FILE__) ); $version = DB::Prepare('select version()', array(), DB::FETCH_TYPE_ROW); @@ -427,7 +430,7 @@ public static function health() { $return[] = array( 'name' => __('Charge', __FILE__), 'state' => ($values[2] < 20), - 'result' => round($values[0],2) . ' - ' . round($values[1],2) . ' - ' . round($values[2],2), + 'result' => round($values[0], 2) . ' - ' . round($values[1], 2) . ' - ' . round($values[2], 2), 'comment' => '', 'key' => 'load' ); @@ -731,36 +734,42 @@ public static function getUsbMapping($_name = '', $_getGPIO = false) { } $usbMapping = self::getUsbLegacy($usbMapping); if ($_getGPIO) { + if (file_exists('/dev/ttyLuna-Zigbee')) { + $usbMapping['Jeedom Luna Zigbee'] = '/dev/ttyLuna-Zigbee'; + } if (file_exists('/dev/ttyS0')) { $usbMapping['Cubiboard'] = '/dev/ttyS0'; } if (file_exists('/dev/ttyS1')) { $usbMapping['Jeedom Luna Zwave'] = '/dev/ttyS1'; - } - if (file_exists('/dev/ttyS1')) { - $usbMapping['Odroid C2'] = '/dev/ttyS1'; + $usbMapping['Odroid (old)'] = '/dev/ttyS1'; } if (file_exists('/dev/ttyS2')) { $usbMapping['Jeedom Atlas'] = '/dev/ttyS2'; + $usbMapping['Rock Pi'] = '/dev/ttyS2'; } if (file_exists('/dev/ttyS3')) { - $usbMapping['Orange PI'] = '/dev/ttyS3'; + $usbMapping['Orange Pi'] = '/dev/ttyS3'; } if (file_exists('/dev/ttymxc0')) { $usbMapping['Jeedom board'] = '/dev/ttymxc0'; } - if (file_exists('/dev/ttyAML1')) { - $usbMapping['Odroid ARMBIAN (Buster)'] = '/dev/ttyAML1'; - } if (file_exists('/dev/ttyAMA0')) { - $usbMapping['Raspberry pi'] = '/dev/ttyAMA0'; + $usbMapping['Raspberry Pi'] = '/dev/ttyAMA0'; + } + if (file_exists('/dev/ttyAML1')) { + $usbMapping['Jeedom Smart'] = '/dev/ttyAML1'; + $usbMapping['Odroid'] = '/dev/ttyAML1'; } if (file_exists('/dev/S2')) { - $usbMapping['Banana PI'] = '/dev/S2'; + $usbMapping['Banana Pi'] = '/dev/S2'; } foreach (ls('/dev/', 'ttyAMA*') as $value) { $usbMapping['/dev/' . $value] = '/dev/' . $value; } + foreach (ls('/dev/', 'ttyAML*') as $value) { + $usbMapping['/dev/' . $value] = '/dev/' . $value; + } } cache::set('jeedom::usbMapping', json_encode($usbMapping)); } else { @@ -1036,18 +1045,6 @@ public static function isDateOk() { return false; } } - $minDateValue = new \DateTime('2020-01-01'); - $mindate = strtotime($minDateValue->format('Y-m-d 00:00:00')); - $maxDateValue = $minDateValue->modify('+6 year')->format('Y-m-d 00:00:00'); - $maxdate = strtotime($maxDateValue); - if (strtotime('now') < $mindate || strtotime('now') > $maxdate) { - self::forceSyncHour(); - sleep(3); - if (strtotime('now') < $mindate || strtotime('now') > $maxdate) { - log::add('core', 'error', __('La date du système est incorrecte (avant ' . $minDateValue . ' ou après ' . $maxDateValue . ') :', __FILE__) . ' ' . (new \DateTime())->format('Y-m-d H:i:s'), 'dateCheckFailed'); - return false; - } - } return true; } @@ -1252,8 +1249,8 @@ public static function cronDaily() { log::add('jeedom', 'error', log::exception($e)); } $disk_space = self::checkSpaceLeft(); - if($disk_space < 10){ - log::add('jeedom', 'error',__('Espace disque disponible faible : ',__FILE__).$disk_space.'%.'.__('Veuillez faire de la place (suppression de backup, de video/capture du plugin camera, d\'historique...)',__FILE__)); + if ($disk_space < 10) { + log::add('jeedom', 'error', __('Espace disque disponible faible : ', __FILE__) . $disk_space . '%.' . __('Veuillez faire de la place (suppression de backup, de video/capture du plugin camera, d\'historique...)', __FILE__)); } } @@ -1309,6 +1306,13 @@ public static function cronHourly() { } catch (Error $e) { log::add('jeedom', 'error', log::exception($e)); } + try { + log::chunk('', True); + } catch (Throwable $e) { + log::add('jeedom', 'error', $e->getMessage()); + } catch (Error $e) { + log::add('jeedom', 'error', $e->getMessage()); + } } /*************************************************************************************/ @@ -1549,7 +1553,7 @@ public static function massReplace($_options = array(), $_eqlogics = array(), $_ if (count($_eqlogics) == 0 && count($_cmds) == 0) { throw new Exception('{{Aucun équipement ou commande à remplacer ou copier}}'); } - foreach (['copyEqProperties', 'hideEqs', 'copyCmdProperties', 'removeCmdHistory', 'copyCmdHistory','disableEqs'] as $key) { + foreach (['copyEqProperties', 'hideEqs', 'copyCmdProperties', 'removeCmdHistory', 'copyCmdHistory', 'disableEqs'] as $key) { if (!isset($_options[$key])) { $_options[$key] = false; } @@ -1636,7 +1640,7 @@ public static function massReplace($_options = array(), $_eqlogics = array(), $_ $targetEq->save(); $return['eqlogics'] += 1; } - } + } if ($_options['hideEqs'] == "true") { foreach ($_eqlogics as $_sourceId => $_targetId) { $sourceEq = eqLogic::byId($_sourceId); @@ -1747,7 +1751,7 @@ public static function checkSpaceLeft($_dir = null) { } public static function getTmpFolder($_plugin = '') { - if(isset(self::$cache['getTmpFolder::' . $_plugin])){ + if (isset(self::$cache['getTmpFolder::' . $_plugin])) { return self::$cache['getTmpFolder::' . $_plugin]; } $return = '/' . trim(config::byKey('folder::tmp'), '/'); @@ -1804,7 +1808,7 @@ public static function getHardwareName() { $result = 'Atlas'; } else if (strpos($hostname, 'Luna') !== false) { $result = 'Luna'; - } else if (file_exists('/proc/1/sched') && strpos(shell_exec('cat /proc/1/sched | head -n 1'),'systemd') === false){ + } else if (file_exists('/proc/1/sched') && strpos(shell_exec('cat /proc/1/sched | head -n 1'), 'systemd') === false) { $result = 'docker'; } config::save('hardware_name', $result); diff --git a/core/class/jsonrpcClient.class.php b/core/class/jsonrpcClient.class.php index 5a00138230..c1df3395a5 100644 --- a/core/class/jsonrpcClient.class.php +++ b/core/class/jsonrpcClient.class.php @@ -138,21 +138,22 @@ private function send($_request, $_timeout = 15, $_file = null, $_maxRetry = 2) curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } - if(config::byKey('proxyEnabled')) { - if(config::byKey('proxyAddress') == ''){ - // throw new Exception(__('renseigne l\'adresse', __FILE__)); - $this->error = 'Erreur address '; - } else if (config::byKey('proxyPort') == ''){ - // throw new Exception(__('renseigne le port', __FILE__)); - } else { - curl_setopt($ch, CURLOPT_PROXY, config::byKey('proxyAddress')); - curl_setopt($ch, CURLOPT_PROXYPORT, config::byKey('proxyPort')); - if(!empty(config::byKey('proxyLogin') || config::byKey('proxyPassword'))){ - curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'proxyLogin:proxyPassword'); + if (config::byKey('proxyEnabled') == 1) { + $proxyAddress = config::byKey('proxyAddress'); + $proxyPort = config::byKey('proxyPort'); + if ($proxyAddress != '' && $proxyPort != '') { + curl_setopt($ch, CURLOPT_PROXY, $proxyAddress); + curl_setopt($ch, CURLOPT_PROXYPORT, $proxyPort); + $proxyLogin = config::byKey('proxyLogin'); + $proxyPassword = config::byKey('proxyPassword'); + if (!empty($proxyLogin) || !empty($proxyPassword)) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyLogin . ':' . $proxyPassword); + } + } else { + $this->error = 'Proxy enabled but address or port is missing'; + log::add('connection', 'error', $this->error); } - log::add('Connection', 'debug', $ch); - } - } + } $response = curl_exec($ch); $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $nbRetry++; diff --git a/core/class/log.class.php b/core/class/log.class.php index 4660c278ff..c279114e79 100644 --- a/core/class/log.class.php +++ b/core/class/log.class.php @@ -59,7 +59,7 @@ public function log($level, $message, array $context = array()) { public static function getConfig($_key, $_default = '') { if (self::$config === null) { - self::$config = array_merge(config::getLogLevelPlugin(), config::byKeys(array('log::engine', 'log::formatter', 'log::level', 'addMessageForErrorLog', 'maxLineLog'))); + self::$config = array_merge(config::getLogLevelPlugin(), config::byKeys(array('log::engine', 'log::formatter', 'log::level', 'addMessageForErrorLog', 'maxLineLog', 'maxSizeLog'))); } if (isset(self::$config[$_key])) { return self::$config[$_key]; @@ -69,9 +69,9 @@ public static function getConfig($_key, $_default = '') { public static function getLogLevel($_log) { $specific_level = self::getConfig('log::level::' . $_log); - if (!is_array($specific_level) && strpos($_log,'_') !== false) { + if (!is_array($specific_level) && strpos($_log, '_') !== false) { preg_match('/(.*?)\_[a-zA-Z]*?$/m', $_log, $matches); - if(isset($matches[1])){ + if (isset($matches[1])) { $specific_level = self::getConfig('log::level::' . $matches[1]); } } @@ -96,7 +96,7 @@ public static function convertLogLevel($_level = 100) { return 'none'; } foreach (self::$level as $key => $value) { - if($value == $_level){ + if ($value == $_level) { return $key; } } @@ -108,36 +108,36 @@ public static function convertLogLevel($_level = 100) { * @param string $_type message type (info, debug, warning, danger) * @param string $_message message added into log */ - public static function add($_log, $_type, $_message, $_logicalId = '') { + public static function add(string $_log, string $_type, string $_message, string $_logicalId = '') { if (trim($_message) == '') { return; } $level = (isset(self::$level[strtolower($_type)])) ? self::$level[strtolower($_type)] : 100; - if($level < self::getLogLevel($_log)){ + if ($level < self::getLogLevel($_log)) { return; } $fp = fopen(self::getPathToLog($_log), 'a'); - fwrite($fp,'['.date('Y-m-d H:i:s').']['.strtoupper($_type).'] '.$_message."\n"); + fwrite($fp, '[' . date('Y-m-d H:i:s') . '][' . strtoupper($_type) . '] ' . $_message . "\n"); fclose($fp); try { - $action = '' . __('Log', __FILE__) . ' ' . $_log . ''; + $action = '' . __('Log', __FILE__) . ' ' . $_log . ''; if ($level == 400 && self::getConfig('addMessageForErrorLog') == 1) { @message::add($_log, $_message, $action, $_logicalId); } elseif ($level >= 500 && $_log != 'update') { @message::add($_log, $_message, $action, $_logicalId); } } catch (Exception $e) { - } } - public static function chunk($_log = '') { + public static function chunk($_log = '', $_onlyIfSizeExceeded = False) { $paths = array(); if ($_log != '') { $paths = array(self::getPathToLog($_log)); } else { $relativeLogPaths = array('', 'scenarioLog/'); foreach ($relativeLogPaths as $relativeLogPath) { + $logPath = self::getPathToLog($relativeLogPath); $logs = ls($logPath, '*'); foreach ($logs as $log) { @@ -147,6 +147,9 @@ public static function chunk($_log = '') { } foreach ($paths as $path) { if (is_file($path)) { + if ($_onlyIfSizeExceeded && filesize($path) < (self::getConfig('maxSizeLog') * 1024 * 1024)) { + continue; + } self::chunkLog($path); } } @@ -161,7 +164,7 @@ public static function chunkLog($_path) { $maxLineLog = self::DEFAULT_MAX_LINE; } try { - com_shell::execute(system::getCmdSudo() . 'chmod 664 ' . $_path . ' > /dev/null 2>&1;'. system::getCmdSudo() . 'chown -R ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . $_path.' > /dev/null 2>&1;'.system::getCmdSudo() . ' echo "$(tail -n ' . $maxLineLog . ' ' . $_path . ')" > ' . $_path); + com_shell::execute(system::getCmdSudo() . 'chmod 664 ' . $_path . ' > /dev/null 2>&1;' . system::getCmdSudo() . 'chown -R ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . $_path . ' > /dev/null 2>&1;' . system::getCmdSudo() . ' echo "$(tail -n ' . $maxLineLog . ' ' . $_path . ')" > ' . $_path); } catch (\Exception $e) { } @chown($_path, system::get('www-uid')); @@ -184,77 +187,144 @@ public static function getPathToLog($_log = 'core') { /** * Check authorisation to emptying log file */ - public static function authorizeClearLog($_log, $_subPath = '') { + public static function authorizeClearLog(string $_log, string $_subPath = ''): bool { $path = self::getPathToLog($_subPath . $_log); - return !((strpos($_log, '.htaccess') !== false) - || (!file_exists($path) || !is_file($path))); + return !((strpos($_log, '.htaccess') !== false) || (!file_exists($path) || !is_file($path))); } /** * Empty log file */ - public static function clear($_log) { + public static function clear(string $_log) { if (self::authorizeClearLog($_log)) { $path = self::getPathToLog($_log); - com_shell::execute(system::getCmdSudo() . 'chmod 664 ' . $path . '> /dev/null 2>&1;'. system::getCmdSudo() . 'chown -R ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . $path.' > /dev/null 2>&1;'.system::getCmdSudo() . ' cat /dev/null > ' . $path); + com_shell::execute(system::getCmdSudo() . 'chmod 664 ' . $path . '> /dev/null 2>&1;' . system::getCmdSudo() . 'chown -R ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . $path . ' > /dev/null 2>&1;' . system::getCmdSudo() . ' cat /dev/null > ' . $path); return true; } return; } - public static function clearAll() { + public static function clearAll(): void { foreach (ls(self::getPathToLog(''), '*', false, array('files')) as $log) self::clear($log); + return; + } + + private static function getActiveDaemonPluginIdsSortedByLength(): array { + static $activePluginsWithDaemon = null; + if ($activePluginsWithDaemon === null) { + $activePluginsWithDaemon = []; + foreach (plugin::listPlugin(true) as $plugin) { + if ($plugin->getHasOwnDeamon() == 0) continue; + $activePluginsWithDaemon[] = $plugin->getId(); + } + usort($activePluginsWithDaemon, function ($a, $b) { + return strlen($b) - strlen($a); + }); + } + + return $activePluginsWithDaemon; + } + + private static function canRemoveLog(string $_log): bool { + if (strpos($_log, 'nginx.error') !== false || strpos($_log, 'http.error') !== false) { + return false; + } + + if (substr($_log, -9) === '_packages' || substr($_log, -7) === '_update') { + return true; + } + + // if a new core log is added to the codebase, add it here to prevent a plugin with the same name from protecting it by mistake. + static $coreLogNames = [ + 'api', + 'apipro', + 'backup', + 'cmd', + 'connection', + 'cron', + 'cron_execution', + 'debug_translate', + 'design', + 'event', + 'expression', + 'history', + 'http.com', + 'interact', + 'jeedom', + 'jeedomAlert', + 'jeeEvent', + 'listener', + 'listener_execution', + 'market', + 'monitoring_cloud', + 'network', + 'plugin', + 'queue', + 'report', + 'scenario', + 'starting', + 'tts', + 'update', + ]; + if (in_array($_log, $coreLogNames)) { + return true; + } + + $plugins = self::getActiveDaemonPluginIdsSortedByLength(); + foreach ($plugins as $plugin) { + if (strpos($_log, $plugin) === 0) { + if ($_log !== $plugin) { + return false; + } + return true; + } + } + return true; } /** * Delete log file */ - public static function remove($_log) { - if (strpos($_log, 'nginx.error') !== false || strpos($_log, 'http.error') !== false) { - self::clear($_log); - return; - } - if (endsWith($_log, 'd') || endsWith($_log, 'd_1') || endsWith($_log, 'd_2') || endsWith($_log, 'd_3')) { + public static function remove(string $_log) { + if (!self::canRemoveLog($_log)) { self::clear($_log); return; } + if (self::authorizeClearLog($_log)) { $path = self::getPathToLog($_log); - com_shell::execute(system::getCmdSudo() . 'chmod 664 ' . $path . ' > /dev/null 2>&1;'. system::getCmdSudo() . 'chown -R ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . $path.' > /dev/null 2>&1;'.system::getCmdSudo() . ' cat /dev/null > ' . $path.';rm ' . $path . ' 2>&1 > /dev/null'); - - - + com_shell::execute(system::getCmdSudo() . 'chmod 664 ' . $path . ' > /dev/null 2>&1;' . system::getCmdSudo() . 'chown -R ' . system::get('www-uid') . ':' . system::get('www-gid') . ' ' . $path . ' > /dev/null 2>&1;' . system::getCmdSudo() . ' cat /dev/null > ' . $path . ';rm ' . $path . ' 2>&1 > /dev/null'); return true; } } - public static function removeAll() { + public static function removeAll(): void { foreach (ls(self::getPathToLog(''), '*', false, array('files')) as $log) self::remove($log); - return true; + return; } /** - * Get $_nbLines from a $_log from $_begin position - * @param string $_log - * @param int $_begin - * @param int $_nbLines - * @return boolean|array - * @deprecated v4.4 - * => removed in v4.6 (use log::getDelta() instead) - * - * Note that log::get($_log, $_begin, $_nbLines) is equivalent to: - * $path = (!file_exists($_log) || !is_file($_log)) ? self::getPathToLog($_log) : $_log; - * if (!file_exists($path)) { - * return false; - * } - * $delta = self::getDelta($_log, $_begin, '', false, false, 0, $_nbLines); - * $arr = explode("\n", $delta['logText']); - * unset($arr[count($arr) - 1]); - * $res = array_reverse($arr); - */ + * Get $_nbLines from a $_log from $_begin position + * @param string $_log + * @param int $_begin + * @param int $_nbLines + * @return boolean|array + * @deprecated v4.4 + * => removed in v4.6 (use log::getDelta() instead) + * + * Note that log::get($_log, $_begin, $_nbLines) is equivalent to: + * $path = (!file_exists($_log) || !is_file($_log)) ? self::getPathToLog($_log) : $_log; + * if (!file_exists($path)) { + * return false; + * } + * $delta = self::getDelta($_log, $_begin, '', false, false, 0, $_nbLines); + * $arr = explode("\n", $delta['logText']); + * unset($arr[count($arr) - 1]); + * $res = array_reverse($arr); + */ public static function get($_log, $_begin, $_nbLines) { $path = (!file_exists($_log) || !is_file($_log)) ? self::getPathToLog($_log) : $_log; if (!file_exists($path)) { @@ -270,7 +340,6 @@ public static function get($_log, $_begin, $_nbLines) { $line = trim($log->current()); //get current line if ($line != '') { array_unshift($page, mb_convert_encoding($line, 'UTF-8')); - } $log->next(); $linesRead++; @@ -280,18 +349,18 @@ public static function get($_log, $_begin, $_nbLines) { } /** - * Get the log delta from $_position to the end of the file - * New position is stored in $_position when eof is reached - * - * @param string $_log Log filename (default 'core') - * @param int $_position Bytes representing position from the begining of the file (default 0) - * @param string $_search Text to find in log file (default '') - * @param bool $_colored Should lines be colored (default false) - * @param bool $_numbered Should lines be numbered (default true) - * @param int $_numStart At what number should lines number start (default 0) - * @param int $_max Max number of returned lines (default is config value "maxLineLog") - * @return array Array containing log to append to buffer and new position for next call - */ + * Get the log delta from $_position to the end of the file + * New position is stored in $_position when eof is reached + * + * @param string $_log Log filename (default 'core') + * @param int $_position Bytes representing position from the begining of the file (default 0) + * @param string $_search Text to find in log file (default '') + * @param bool $_colored Should lines be colored (default false) + * @param bool $_numbered Should lines be numbered (default true) + * @param int $_numStart At what number should lines number start (default 0) + * @param int $_max Max number of returned lines (default is config value "maxLineLog") + * @return array Array containing log to append to buffer and new position for next call + */ public static function getDelta($_log = 'core', $_position = 0, $_search = '', $_colored = false, $_numbered = true, $_numStart = 0, $_max = -1) { // Add path to file if needed $filename = (file_exists($_log) && is_file($_log)) ? $_log : self::getPathToLog($_log); @@ -359,46 +428,82 @@ public static function getDelta($_log = 'core', $_position = 0, $_search = '', $ $logText = preg_replace('/(' . $srch . ')/i', '$1', $logText); } - $search = array(); $replace = array(); - $search[] = '[DEBUG]'; $replace[] = ' D<&>EBUG '; - $search[] = '[INFO]'; $replace[] = ' I<&>NFO '; - $search[] = '[NOTICE]'; $replace[] = 'N<&>OTICE '; - $search[] = '[WARNING]'; $replace[] = 'W<&>ARNING'; - $search[] = '[ERROR]'; $replace[] = ' E<&>RROR '; - $search[] = '[CRITICAL]'; $replace[] = ' C<&>RITI '; - $search[] = '[ALERT]'; $replace[] = ' A<&>LERT '; - $search[] = '[EMERGENCY]'; $replace[] = ' E<&>MERG '; - - $search[] = '[ OK ]'; $replace[] = '[ O<&>K ]'; - $search[] = '[ KO ]'; $replace[] = '[ K<&>O ]'; - $search[] = ' OK '; $replace[] = ' O<&>K '; - $search[] = ' KO '; $replace[] = ' K<&>O '; - $search[] = 'ERROR'; $replace[] = 'E<&>RROR'; - $search[] = 'PHP Notice:'; $replace[] = 'PHP N<&>otice:'; - $search[] = 'PHP Warning:'; $replace[] = 'PHP War<&>ning:'; - $search[] = 'PHP Stack trace:'; $replace[] = 'PHP S<&>tack trace:'; - - $search[] = ':br:'; $replace[] = ''; - $search[] = ':bg-success:'; $replace[] = ''; - $search[] = ':bg-info:'; $replace[] = ''; - $search[] = ':bg-warning:'; $replace[] = ''; - $search[] = ':bg-danger:'; $replace[] = ''; - $search[] = ':/bg:'; $replace[] = ''; - $search[] = ':fg-success:'; $replace[] = ''; - $search[] = ':fg-info:'; $replace[] = ''; - $search[] = ':fg-warning:'; $replace[] = ''; - $search[] = ':fg-danger:'; $replace[] = ''; - $search[] = ':/fg:'; $replace[] = ''; - $search[] = ':b:'; $replace[] = ''; - $search[] = ':/b:'; $replace[] = ''; - $search[] = ':s:'; $replace[] = ''; - $search[] = ':/s:'; $replace[] = ''; - $search[] = ':i:'; $replace[] = ''; - $search[] = ':/i:'; $replace[] = ''; - $search[] = ':hide:'; $replace[] = ''; - - foreach($GLOBALS['JEEDOM_SCLOG_TEXT'] as $item) { + $search = array(); + $replace = array(); + $search[] = '[DEBUG]'; + $replace[] = ' D<&>EBUG '; + $search[] = '[INFO]'; + $replace[] = ' I<&>NFO '; + $search[] = '[NOTICE]'; + $replace[] = 'N<&>OTICE '; + $search[] = '[WARNING]'; + $replace[] = 'W<&>ARNING'; + $search[] = '[ERROR]'; + $replace[] = ' E<&>RROR '; + $search[] = '[CRITICAL]'; + $replace[] = ' C<&>RITI '; + $search[] = '[ALERT]'; + $replace[] = ' A<&>LERT '; + $search[] = '[EMERGENCY]'; + $replace[] = ' E<&>MERG '; + + $search[] = '[ OK ]'; + $replace[] = '[ O<&>K ]'; + $search[] = '[ KO ]'; + $replace[] = '[ K<&>O ]'; + $search[] = ' OK '; + $replace[] = ' O<&>K '; + $search[] = ' KO '; + $replace[] = ' K<&>O '; + $search[] = 'ERROR'; + $replace[] = 'E<&>RROR'; + $search[] = 'PHP Notice:'; + $replace[] = 'PHP N<&>otice:'; + $search[] = 'PHP Warning:'; + $replace[] = 'PHP War<&>ning:'; + $search[] = 'PHP Stack trace:'; + $replace[] = 'PHP S<&>tack trace:'; + + $search[] = ':br:'; + $replace[] = ''; + $search[] = ':bg-success:'; + $replace[] = ''; + $search[] = ':bg-info:'; + $replace[] = ''; + $search[] = ':bg-warning:'; + $replace[] = ''; + $search[] = ':bg-danger:'; + $replace[] = ''; + $search[] = ':/bg:'; + $replace[] = ''; + $search[] = ':fg-success:'; + $replace[] = ''; + $search[] = ':fg-info:'; + $replace[] = ''; + $search[] = ':fg-warning:'; + $replace[] = ''; + $search[] = ':fg-danger:'; + $replace[] = ''; + $search[] = ':/fg:'; + $replace[] = ''; + $search[] = ':b:'; + $replace[] = ''; + $search[] = ':/b:'; + $replace[] = ''; + $search[] = ':s:'; + $replace[] = ''; + $search[] = ':/s:'; + $replace[] = ''; + $search[] = ':i:'; + $replace[] = ''; + $search[] = ':/i:'; + $replace[] = ''; + $search[] = ':hide:'; + $replace[] = ''; + + foreach ($GLOBALS['JEEDOM_SCLOG_TEXT'] as $item) { $search[] = $item['txt']; // Insert a marker into subject string to avoid replacing it multiple times $subject = $item['txt'][0] . '<&>' . substr($item['txt'], 1); @@ -413,7 +518,7 @@ public static function getDelta($_log = 'core', $_position = 0, $_search = '', $ array('txt' => '-------------------- TRUNCATED LOG --------------------', 'replace' => '::') ); - foreach($replacables as $item) { + foreach ($replacables as $item) { if (strlen($item['txt']) >= 2) { $search[] = $item['txt']; // Insert a marker into subject string to avoid replacing it multiple times @@ -432,10 +537,10 @@ public static function getDelta($_log = 'core', $_position = 0, $_search = '', $ } /** - * Efficiently get the last line of a file - * @param string $_log Log filename - * @return string The last non-empty line of the file (or '') - */ + * Efficiently get the last line of a file + * @param string $_log Log filename + * @return string The last non-empty line of the file (or '') + */ public static function getLastLine($_log) { // Add path to file if needed $filename = (file_exists($_log) && is_file($_log)) ? $_log : self::getPathToLog($_log); @@ -512,7 +617,7 @@ public static function exception($e) { if (self::getConfig('log::level') > 100) { return $e->getMessage(); } else { - return $e->getMessage()."\n".$e->getTraceAsString(); + return $e->getMessage() . "\n" . $e->getTraceAsString(); } } diff --git a/core/class/plugin.class.php b/core/class/plugin.class.php index 8f43d1e354..a31c88fd3a 100644 --- a/core/class/plugin.class.php +++ b/core/class/plugin.class.php @@ -57,10 +57,10 @@ class plugin { /* * ***********************Méthodes statiques*************************** */ - public static function byId($_id,$_full = false) { + public static function byId($_id, $_full = false) { global $JEEDOM_INTERNAL_CONFIG; - if (is_string($_id) && isset(self::$_cache[$_id.'::'.$_full])) { - return self::$_cache[$_id.'::'.$_full]; + if (is_string($_id) && isset(self::$_cache[$_id . '::' . $_full])) { + return self::$_cache[$_id . '::' . $_full]; } if (!file_exists($_id) || strpos($_id, '/') === false) { $path = self::getPathById($_id); @@ -134,19 +134,19 @@ public static function byId($_id,$_full = false) { 'type' => 'class', ); } - - - $plugin->functionality['interact'] = array('exists' => method_exists($plugin->getId(), 'interact'), 'controlable' => 1); - $plugin->functionality['cron'] = array('exists' => method_exists($plugin->getId(), 'cron'), 'controlable' => 1); - $plugin->functionality['cron5'] = array('exists' => method_exists($plugin->getId(), 'cron5'), 'controlable' => 1); - $plugin->functionality['cron10'] = array('exists' => method_exists($plugin->getId(), 'cron10'), 'controlable' => 1); - $plugin->functionality['cron15'] = array('exists' => method_exists($plugin->getId(), 'cron15'), 'controlable' => 1); - $plugin->functionality['cron30'] = array('exists' => method_exists($plugin->getId(), 'cron30'), 'controlable' => 1); - $plugin->functionality['cronHourly'] = array('exists' => method_exists($plugin->getId(), 'cronHourly'), 'controlable' => 1); - $plugin->functionality['cronDaily'] = array('exists' => method_exists($plugin->getId(), 'cronDaily'), 'controlable' => 1); - $plugin->functionality['deadcmd'] = array('exists' => method_exists($plugin->getId(), 'deadCmd'), 'controlable' => 0); - $plugin->functionality['health'] = array('exists' => method_exists($plugin->getId(), 'health'), 'controlable' => 0); - + + + $plugin->functionality['interact'] = array('exists' => method_exists($plugin->getId(), 'interact'), 'controlable' => 1); + $plugin->functionality['cron'] = array('exists' => method_exists($plugin->getId(), 'cron'), 'controlable' => 1); + $plugin->functionality['cron5'] = array('exists' => method_exists($plugin->getId(), 'cron5'), 'controlable' => 1); + $plugin->functionality['cron10'] = array('exists' => method_exists($plugin->getId(), 'cron10'), 'controlable' => 1); + $plugin->functionality['cron15'] = array('exists' => method_exists($plugin->getId(), 'cron15'), 'controlable' => 1); + $plugin->functionality['cron30'] = array('exists' => method_exists($plugin->getId(), 'cron30'), 'controlable' => 1); + $plugin->functionality['cronHourly'] = array('exists' => method_exists($plugin->getId(), 'cronHourly'), 'controlable' => 1); + $plugin->functionality['cronDaily'] = array('exists' => method_exists($plugin->getId(), 'cronDaily'), 'controlable' => 1); + $plugin->functionality['deadcmd'] = array('exists' => method_exists($plugin->getId(), 'deadCmd'), 'controlable' => 0); + $plugin->functionality['health'] = array('exists' => method_exists($plugin->getId(), 'health'), 'controlable' => 0); + $plugin->functionality['interact'] = array('exists' => method_exists($plugin->getId(), 'interact'), 'controlable' => 1); $plugin->functionality['cron'] = array('exists' => method_exists($plugin->getId(), 'cron'), 'controlable' => 1); $plugin->functionality['cron5'] = array('exists' => method_exists($plugin->getId(), 'cron5'), 'controlable' => 1); @@ -157,11 +157,11 @@ public static function byId($_id,$_full = false) { $plugin->functionality['cronDaily'] = array('exists' => method_exists($plugin->getId(), 'cronDaily'), 'controlable' => 1); $plugin->functionality['deadcmd'] = array('exists' => method_exists($plugin->getId(), 'deadCmd'), 'controlable' => 0); $plugin->functionality['health'] = array('exists' => method_exists($plugin->getId(), 'health'), 'controlable' => 0); - if($_full){ - if($plugin->getCache('usedSpace',-1) == -1){ - $plugin->setCache('usedSpace',getDirectorySize(__DIR__ . '/../../plugins/' . $data['id']),86400); + if ($_full) { + if ($plugin->getCache('usedSpace', -1) == -1) { + $plugin->setCache('usedSpace', getDirectorySize(__DIR__ . '/../../plugins/' . $data['id']), 86400); } - $plugin->usedSpace = $plugin->getCache('usedSpace',-1); + $plugin->usedSpace = $plugin->getCache('usedSpace', -1); } if (!isset($JEEDOM_INTERNAL_CONFIG['plugin']['category'][$plugin->category])) { foreach ($JEEDOM_INTERNAL_CONFIG['plugin']['category'] as $key => $value) { @@ -174,7 +174,7 @@ public static function byId($_id,$_full = false) { } } } - self::$_cache[$plugin->id.'::'.$_full] = $plugin; + self::$_cache[$plugin->id . '::' . $_full] = $plugin; return $plugin; } @@ -619,14 +619,14 @@ public static function checkDeamon() { } } - public static function isInstalled($_pluginId): bool { - try { - plugin::byId($_pluginId); - return true; - } catch (Exception $e) { - return false; - } - } + public static function isInstalled($_pluginId): bool { + try { + plugin::byId($_pluginId); + return true; + } catch (Exception $e) { + return false; + } + } /* * *********************Méthodes d'instance************************* */ @@ -689,7 +689,7 @@ public function callInstallFunction($_function, $_direct = false) { public function dependancy_info($_refresh = false) { $plugin_id = $this->getId(); - $cache = cache::byKey('dependancy' . $this->getID()); + $cache = cache::byKey('dependancy' . $this->getId()); if ($_refresh) { $cache->remove(); } else { @@ -725,14 +725,16 @@ public function dependancy_info($_refresh = false) { } $return['last_launch'] = config::byKey('lastDependancyInstallTime', $this->getId(), __('Inconnue', __FILE__)); $return['auto'] = config::byKey('dependancyAutoMode', $this->getId(), 1); - if ($return['state'] != 'in_progress' && method_exists($plugin_id, 'additionnalDependancyCheck')) { + + if ($return['state'] === 'ok' && method_exists($plugin_id, 'additionnalDependancyCheck')) { $additionnal = $plugin_id::additionnalDependancyCheck(); if (isset($additionnal['state'])) { $return['state'] = $additionnal['state']; } } - if ($return['state'] == 'ok') { - cache::set('dependancy' . $this->getID(), $return); + + if ($return['state'] === 'ok') { + cache::set('dependancy' . $this->getId(), $return); } return $return; } @@ -764,7 +766,7 @@ public function dependancy_info($_refresh = false) { $return['last_launch'] = config::byKey('lastDependancyInstallTime', $this->getId(), __('Inconnue', __FILE__)); $return['auto'] = config::byKey('dependancyAutoMode', $this->getId(), 1); if ($return['state'] == 'ok') { - cache::set('dependancy' . $this->getID(), $return); + cache::set('dependancy' . $this->getId(), $return); } return $return; } @@ -776,7 +778,7 @@ public function dependancy_info($_refresh = false) { public function dependancy_install($_force = false, $_foreground = false) { $plugin_id = $this->getId(); if (!$_force && config::byKey('dontProtectTooFastLaunchDependancy') == 0 && abs(strtotime('now') - strtotime(config::byKey('lastDependancyInstallTime', $plugin_id))) <= 60) { - $cache = cache::byKey('dependancy' . $this->getID()); + $cache = cache::byKey('dependancy' . $this->getId()); $cache->remove(); throw new Exception(__('Vous devez attendre au moins 60 secondes entre deux lancements d\'installation de dépendances', __FILE__)); } @@ -788,7 +790,7 @@ public function dependancy_install($_force = false, $_foreground = false) { $this->deamon_stop(); config::save('lastDependancyInstallTime', date('Y-m-d H:i:s'), $plugin_id); system::checkAndInstall(json_decode(file_get_contents(__DIR__ . '/../../plugins/' . $plugin_id . '/plugin_info/packages.json'), true), true, $_foreground, $plugin_id, $_force); - $cache = cache::byKey('dependancy' . $this->getID()); + $cache = cache::byKey('dependancy' . $this->getId()); $cache->remove(); return; } @@ -831,7 +833,7 @@ public function dependancy_install($_force = false, $_foreground = false) { log::add($plugin_id, 'error', __('Aucun script ne correspond à votre type de Linux :', __FILE__) . ' ' . $cmd['script'] . ' ' . __('avec #stype# :', __FILE__) . ' ' . system::get('type')); } } - $cache = cache::byKey('dependancy' . $this->getID()); + $cache = cache::byKey('dependancy' . $this->getId()); $cache->remove(); return; } @@ -966,9 +968,9 @@ public function setIsEnable($_state, $_force = false, $_foreground = false) { } $osVersion = $this->getRequireOsVersion(); $distrib = system::getDistrib(); - if(isset($osVersion)){ + if (isset($osVersion)) { if ($distrib == 'debian' && version_compare(system::getOsVersion(), $osVersion) == -1 && $_state == 1) { - throw new Exception(__('Votre version Debian n\'est pas assez récente pour activer cette version du plugin, '.$osVersion.' minimum demandé', __FILE__)); + throw new Exception(__('Votre version Debian n\'est pas assez récente pour activer cette version du plugin, ' . $osVersion . ' minimum demandé', __FILE__)); } } $alreadyActive = config::byKey('active', $this->getId(), 0); diff --git a/core/class/scenario.class.php b/core/class/scenario.class.php index 2b96c67bec..c8385ed3a4 100644 --- a/core/class/scenario.class.php +++ b/core/class/scenario.class.php @@ -912,16 +912,20 @@ public function execute($instance_id = '') { return; } } - $cmd = cmd::byId(str_replace('#', '', $this->getTag('trigger_id'))); - if (is_object($cmd)) { - log::add('event', 'info', __('Exécution du scénario', __FILE__) . ' ' . $this->getHumanName() . ' ' . __('déclenché par :', __FILE__) . ' ' . $cmd->getHumanName()); + if($this->getTag('trigger') == 'scenario'){ + $obj_trigger = scenario::byId(str_replace('#', '', $this->getTag('trigger_id'))); + }else{ + $obj_trigger = cmd::byId(str_replace('#', '', $this->getTag('trigger_id'))); + } + if (is_object($obj_trigger)) { + log::add('event', 'info', __('Exécution du scénario', __FILE__) . ' ' . $this->getHumanName() . ' ' . __('déclenché par :', __FILE__) . ' ' . $obj_trigger->getHumanName()); if ($this->getConfiguration('timeline::enable')) { $timeline = new timeline(); $timeline->setType('scenario'); $timeline->setFolder($this->getConfiguration('timeline::folder')); $timeline->setLink_id($this->getId()); $timeline->setName($this->getHumanName(true, true, true, true)); - $timeline->setOptions(array('trigger' => $cmd->getHumanName(true))); + $timeline->setOptions(array('trigger' => $obj_trigger->getHumanName(true))); $timeline->save(); } } else { @@ -2024,7 +2028,7 @@ public function getTags() { * @return $this */ public function setTags($_tags) { - $this->_tags = $_tags; + $this->_tags = array_merge($this->_tags, $_tags); return $this; } diff --git a/core/class/scenarioExpression.class.php b/core/class/scenarioExpression.class.php index 952352df20..122c407b8e 100644 --- a/core/class/scenarioExpression.class.php +++ b/core/class/scenarioExpression.class.php @@ -194,17 +194,17 @@ public static function getDatesFromPeriod($_period = '1 hour') { if ($_period == 'day') $_period = '1 day'; if (ctype_digit($_period[0]) && !stristr($_period, "ago")) { - $_startTime = date('Y-m-d H:i:s',(int) strtotime('-' . $_period)); + $_startTime = date('Y-m-d H:i:s', (int) strtotime('-' . $_period)); } else { - $_startTime = date('Y-m-d H:i:s',(int) strtotime($_period)); + $_startTime = date('Y-m-d H:i:s', (int) strtotime($_period)); } $_endTime = date('Y-m-d H:i:s'); if ($_period == 'today') { $_startTime = date('Y-m-d') . ' 00:00:00'; } elseif ($_period == 'yesterday') { - $_startTime = date('Y-m-d',(int) strtotime('-1 day')) . ' 00:00:00'; - $_endTime = date('Y-m-d',(int) strtotime('-1 day')) . ' 23:59:59'; + $_startTime = date('Y-m-d', (int) strtotime('-1 day')) . ' 00:00:00'; + $_endTime = date('Y-m-d', (int) strtotime('-1 day')) . ' 23:59:59'; } return array($_startTime, $_endTime); } @@ -213,19 +213,20 @@ public static function randText($_sValue) { $_sValue = self::setTags($_sValue); $_aValue = explode(";", $_sValue); try { - $result = evaluate($_aValue); + $result = evaluate($_sValue); if (is_string($result)) { $result = $_aValue; } } catch (Exception $e) { $result = $_aValue; } - if (is_array($_aValue)) { - $nbr = mt_rand(0, count($_aValue) - 1); - return $_aValue[$nbr]; - } else { - return $_aValue; + if (is_array($result)) { + if (count($result) === 0) { + return ''; + } + return $result[array_rand($result)]; } + return $result; } public static function scenario($_scenario) { @@ -303,8 +304,8 @@ public static function averageBetween($_cmd_id, $_startDate, $_endDate, $_round if (!is_object($cmd) || $cmd->getIsHistorized() == 0) { return ''; } - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); $historyStatistique = $cmd->getStatistique($_startTime, $_endTime); if (!isset($historyStatistique['avg'])) { return ''; @@ -328,8 +329,8 @@ public static function averageTemporalBetween($_cmd_id, $_startDate, $_endDate, if (!is_object($cmd) || $cmd->getIsHistorized() == 0) { return ''; } - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); return round($cmd->getTemporalAvg($_startTime, $_endTime), $_round); } @@ -458,8 +459,8 @@ public static function maxBetween($_cmd_id, $_startDate, $_endDate, $_round = 1) if (!is_object($cmd) || $cmd->getIsHistorized() == 0) { return ''; } - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); $historyStatistique = $cmd->getStatistique($_startTime, $_endTime); if (!isset($historyStatistique['max'])) { return ''; @@ -489,8 +490,8 @@ public static function minBetween($_cmd_id, $_startDate, $_endDate, $_round = 1) if (!is_object($cmd) || $cmd->getIsHistorized() == 0) { return ''; } - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); $historyStatistique = $cmd->getStatistique($_startTime, $_endTime); if (!isset($historyStatistique['min'])) { return ''; @@ -642,8 +643,8 @@ public static function stateChangesBetween($_cmd_id, $_value, $_startDate, $_end $_endDate = func_get_arg(2); $_value = null; } - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); return history::stateChanges($cmd_id, $_value, $_startTime, $_endTime); } @@ -724,8 +725,8 @@ public static function durationBetween($_cmd_id, $_value, $_startDate, $_endDate $_endDate = date('Y-m-d H:i:s'); } - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); $_value = str_replace(',', '.', $_value); $_decimal = strlen(substr(strrchr($_value, "."), 1)); @@ -769,8 +770,8 @@ public static function lastBetween($_cmd_id, $_startDate, $_endDate) { if (!is_object($cmd) || $cmd->getIsHistorized() == 0) { return ''; } - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); $historyStatistique = $cmd->getStatistique($_startTime, $_endTime); if (!isset($historyStatistique['last']) || $historyStatistique['last'] === '') { return ''; @@ -802,8 +803,8 @@ public static function statisticsBetween($_cmd_id, $_calc, $_startDate, $_endDat return ''; } $_calc = str_replace(' ', '', $_calc); - $_startTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_startDate))); - $_endTime = date('Y-m-d H:i:s',(int) strtotime(self::setTags($_endDate))); + $_startTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_startDate))); + $_endTime = date('Y-m-d H:i:s', (int) strtotime(self::setTags($_endDate))); $historyStatistique = $cmd->getStatistique(self::setTags($_startTime), self::setTags($_endTime)); return $historyStatistique[$_calc]; } @@ -960,7 +961,7 @@ public static function trigger($_name = '', &$_scenario = null) { if (trim($_name) == '') { return $_scenario->getTag('trigger_name'); } - if (trim(jeedom::toHumanReadable($_name),'#') == $_scenario->getTag('trigger_name')) { + if (trim(jeedom::toHumanReadable($_name), '#') == $_scenario->getTag('trigger_name')) { return 1; } return 0; @@ -1038,8 +1039,8 @@ public static function time_diff($_date1, $_date2, $_format = 'd', $_rnd = 2) { if ($_date2 < 100) $_date2 = '00' . $_date2; if ($_date2 < 1000) $_date2 = '0' . $_date2; } - $d1 = str_replace(array('"','\'',"'"), '', self::setTags($_date1)); - $d2 = str_replace(array('"','\'',"'"), '', self::setTags($_date2)); + $d1 = str_replace(array('"', '\'', "'"), '', self::setTags($_date1)); + $d2 = str_replace(array('"', '\'', "'"), '', self::setTags($_date2)); $date1 = new DateTime($d1); $date2 = new DateTime($d2); $duree = $date2->getTimestamp() - $date1->getTimestamp(); @@ -1275,6 +1276,9 @@ public static function setTags(&$_expression, &$_scenario = null, $_quote = fals } if ($_quote) { foreach ($replace1 as &$value) { + if ($value === null) { + continue; + } if (strpos($value, ' ') !== false || preg_match("/[a-zA-Z]/", $value) || $value === '') { $value = '"' . trim($value, '"') . '"'; } @@ -1386,7 +1390,7 @@ public function execute(&$scenario = null) { } $this->checkBackground(); if ($this->getOptions('background', 0) == 1) { - $key = 'scenarioElement'.$this->getId().'::' . config::genKey(16).'::'.strtotime('now'); + $key = 'scenarioElement' . $this->getId() . '::' . config::genKey(16) . '::' . strtotime('now'); cache::set($key, array('scenarioExpression' => $this, 'scenario' => $scenario), 60); $cmd = __DIR__ . '/../php/jeeScenarioExpression.php'; $cmd .= ' key=' . $key; @@ -1547,16 +1551,13 @@ public function execute(&$scenario = null) { event::add('changeTheme', $options['theme']); return; } elseif ($this->getExpression() == 'scenario') { - if ($scenario !== null && $this->getOptions('scenario_id') == $scenario->getId()) { - $actionScenario = &$scenario; - } else { - $actionScenario = scenario::byId($this->getOptions('scenario_id')); - } + $actionScenario = scenario::byId($this->getOptions('scenario_id')); if (!is_object($actionScenario)) { throw new Exception($GLOBALS['JEEDOM_SCLOG_TEXT']['unfoundScenario']['txt'] . $this->getOptions('scenario_id')); } switch ($this->getOptions('action')) { case 'start': + case 'startsync': if ($this->getOptions('tags') != '' && !is_array($this->getOptions('tags'))) { $tags = array(); $args = arg2array($this->getOptions('tags')); @@ -1569,45 +1570,19 @@ public function execute(&$scenario = null) { if (is_array($this->getOptions('tags'))) { $actionScenario->setTags($this->getOptions('tags')); } - $this->setLog($scenario, $GLOBALS['JEEDOM_SCLOG_TEXT']['launchScenario']['txt'] . $actionScenario->getName() . ' ' . __('options :', __FILE__) . ' ' . json_encode($actionScenario->getTags())); - if ($scenario !== null) { - $actionScenario->addTag('trigger','scenario'); - $actionScenario->addTag('trigger_message',$GLOBALS['JEEDOM_SCLOG_TEXT']['startByScenario']['txt'] . $scenario->getHumanName()); - $actionScenario->addTag('trigger_name',trim($scenario->getHumanName(),'#')); - $actionScenario->addTag('trigger_id',$scenario->getId()); - return $actionScenario->launch(); - } else { - $actionScenario->addTag('trigger','other'); - $actionScenario->addTag('trigger_message',$GLOBALS['JEEDOM_SCLOG_TEXT']['startCausedBy']['txt']); - return $actionScenario->launch(); - } - break; - case 'startsync': - if ($this->getOptions('tags') != '' && !is_array($this->getOptions('tags'))) { - $tags = array(); - $args = arg2array($this->getOptions('tags')); - foreach ($args as $key => $value) { - $value = trim($value); - $tags['#' . trim(trim($key), '#') . '#'] = trim(self::setTags($value, $scenario), '"'); - } - $actionScenario->setTags($tags); - } - if (is_array($this->getOptions('tags'))) { - $actionScenario->setTags($this->getOptions('tags')); - } - $this->setLog($scenario, $GLOBALS['JEEDOM_SCLOG_TEXT']['launchScenario']['txt'] . $actionScenario->getName() . ' ' . __('options :', __FILE__) . ' ' . json_encode($actionScenario->getTags())); if ($scenario !== null) { - $actionScenario->addTag('trigger','scenario'); - $actionScenario->addTag('trigger_message',$GLOBALS['JEEDOM_SCLOG_TEXT']['startByScenario']['txt'] . $scenario->getHumanName()); - $actionScenario->addTag('trigger_name',trim($scenario->getHumanName(),'#')); - $actionScenario->addTag('trigger_id',$scenario->getId()); - return $actionScenario->launch(true); + $actionScenario->addTag('trigger', 'scenario'); + $actionScenario->addTag('trigger_message', $GLOBALS['JEEDOM_SCLOG_TEXT']['startByScenario']['txt'] . $scenario->getHumanName()); + $actionScenario->addTag('trigger_name', trim($scenario->getHumanName(), '#')); + $actionScenario->addTag('trigger_id', $scenario->getId()); } else { - $actionScenario->addTag('trigger','other'); - $actionScenario->addTag('trigger_message',$GLOBALS['JEEDOM_SCLOG_TEXT']['startCausedBy']['txt']); - return $actionScenario->launch(true); + $actionScenario->addTag('trigger', 'other'); + $actionScenario->addTag('trigger_message', $GLOBALS['JEEDOM_SCLOG_TEXT']['startCausedBy']['txt']); } - break; + $forceSync = ($this->getOptions('action') == 'startsync'); + $launchScenarioLogKey = ($forceSync) ? 'launchScenarioSync' : 'launchScenario'; + $this->setLog($scenario, $GLOBALS['JEEDOM_SCLOG_TEXT'][$launchScenarioLogKey]['txt'] . $actionScenario->getName() . ' ' . __('options :', __FILE__) . ' ' . json_encode($actionScenario->getTags())); + return $actionScenario->launch($forceSync); case 'stop': $this->setLog($scenario, __('Arrêt forcé du scénario :', __FILE__) . ' ' . $actionScenario->getName()); $actionScenario->stop(); @@ -1791,8 +1766,8 @@ public function execute(&$scenario = null) { $tmp_file = jeedom::getTmpFolder('history_export') . '/' . $options['name'] . '.csv'; $cmd_parameters = array('files' => [$tmp_file], 'title' => $options['name'], 'message' => $options['name']); - $start = date('Y-m-d H:i:s',(int) strtotime($options['start'])); - $end = date('Y-m-d H:i:s',(int) strtotime($options['end'])); + $start = date('Y-m-d H:i:s', (int) strtotime($options['start'])); + $end = date('Y-m-d H:i:s', (int) strtotime($options['end'])); $this->setLog($scenario, __('Export de l\'historique du', __FILE__) . ' ' . $start . ' ' . __('au', __FILE__) . ' ' . $end); $histories = array(); diff --git a/core/class/system.class.php b/core/class/system.class.php index af5962d3d7..347876791f 100644 --- a/core/class/system.class.php +++ b/core/class/system.class.php @@ -793,7 +793,7 @@ public static function installPackageInProgress($_plugin = ''): bool { } return true; } - if (shell_exec('ls /tmp/jeedom_install_in_progress* | wc -l') > 0) { + if (shell_exec('ls /tmp/jeedom_install_in_progress* | wc -l 2> /dev/null') > 0) { return true; } return false; @@ -918,4 +918,4 @@ public static function checkInstallationLog($_plugin = ''): string { } return ''; } -} \ No newline at end of file +} diff --git a/core/class/update.class.php b/core/class/update.class.php index c2b5062355..4fa62240b0 100644 --- a/core/class/update.class.php +++ b/core/class/update.class.php @@ -515,7 +515,7 @@ public function checkUpdate() { return; } if (config::byKey('core::repo::provider') == 'default') { - $this->setRemoteVersion(self::getLastAvailableVersion(true)); + $this->setRemoteVersion(self::getLastAvailableVersion()); } else { $class = 'repo_' . config::byKey('core::repo::provider'); if (!method_exists($class, 'versionCore') || config::byKey(config::byKey('core::repo::provider') . '::enable') != 1) { diff --git a/core/class/user.class.php b/core/class/user.class.php index 8f834215c1..95567f5302 100644 --- a/core/class/user.class.php +++ b/core/class/user.class.php @@ -122,7 +122,7 @@ public static function connect(string $_login, string $_mdp) { ->setProfils($profile); $user->save(); log::add("connection", "info", __('User created from the LDAP :', __FILE__) . ' ' . $_login); - jeedom::event('user_connect'); + jeedom::event('user_connect', false, array('trigger_value' => $_login)); // TODO : if username == password => change ldap password log::add('event', 'info', __('User connection accepted', __FILE__) . ' ' . $_login); return $user; @@ -145,7 +145,7 @@ public static function connect(string $_login, string $_mdp) { if (is_object($user)) { $user->setOptions('lastConnection', date('Y-m-d H:i:s')); $user->save(); - jeedom::event('user_connect'); + jeedom::event('user_connect', false, array('trigger_value' => $_login)); log::add('event', 'info', __('Local account found for', __FILE__) . ' ' . $_login); log::add('event', 'info', __('User connection accepted', __FILE__) . ' ' . $_login); } diff --git a/core/config/default.config.ini b/core/config/default.config.ini index 1d54bc82d2..8ad321a6c1 100644 --- a/core/config/default.config.ini +++ b/core/config/default.config.ini @@ -80,6 +80,7 @@ security::whiteips = "127.0.0.1;192.168.*.*;10.*.*.*;172.*.*.*" ;Log maxLineLog = 500 +maxSizeLog = 5 log::level = 400 log::syslogudpport = 514 log::syslogudpfacility = user diff --git a/core/config/version b/core/config/version index 958d30d86d..4e298cc965 100644 --- a/core/config/version +++ b/core/config/version @@ -1 +1 @@ -4.5 \ No newline at end of file +4.5.3 diff --git a/core/js/history.class.js b/core/js/history.class.js index 9af112f932..0d1d80f807 100644 --- a/core/js/history.class.js +++ b/core/js/history.class.js @@ -160,7 +160,7 @@ jeedom.history.graphUpdate = function(_params) { continue; } for(var chart in jeedom.history.chart){ - for(var serie in jeedom.history.chart[chart]){ + for(var serie in jeedom.history.chart[chart].chart.series){ if(jeedom.history.chart[chart].chart.series[serie] && jeedom.history.chart[chart].chart.series[serie].options.id == _params[i].cmd_id){ jeedom.history.chart[chart].chart.series[serie].addPoint([Date.now()+(-1*(new Date()).getTimezoneOffset()*60*1000),_params[i].value]) } diff --git a/core/repo/github.repo.php b/core/repo/github.repo.php index 136428ed4b..f76cf5e179 100644 --- a/core/repo/github.repo.php +++ b/core/repo/github.repo.php @@ -22,19 +22,19 @@ class repo_github { /* * *************************Attributs****************************** */ - + public static $_name = 'Github'; - + public static $_scope = array( 'plugin' => true, 'backup' => false, 'hasConfiguration' => true, 'core' => true, ); - - + + /* * ***********************Méthodes statiques*************************** */ - + public static function getConfigurationOption(){ return array( 'parameters_for_add' => array( @@ -79,7 +79,7 @@ public static function getConfigurationOption(){ ), ); } - + public static function checkUpdate(&$_update) { if (is_array($_update)) { if (count($_update) < 1) { @@ -123,7 +123,7 @@ public static function getBranchInfo($_update){ $request_http->setHeader($headers); return json_decode($request_http->exec(10, 1), true); } - + public static function downloadObject($_update) { $token = $_update->getConfiguration('token',config::byKey('github::token','core','')); $branch = self::getBranchInfo($_update); @@ -141,29 +141,41 @@ public static function downloadObject($_update) { $url = 'https://api.github.com/repos/' . $_update->getConfiguration('user') . '/' . $_update->getConfiguration('repository') . '/zipball/' . $_update->getConfiguration('version', 'master'); log::add('update', 'alert', __('Téléchargement de', __FILE__) . ' ' . $_update->getLogicalId() . '...'); if ($token == '') { - $result = shell_exec('curl -s -L ' . $url . ' > ' . $tmp); + exec('curl -s -f -L -o ' . escapeshellarg($tmp) . ' ' . escapeshellarg($url) . ' 2>&1', $output, $return_code); } else { - $result = shell_exec('curl -s -H "Authorization: token ' . $token . '" -L ' . $url . ' > ' . $tmp); + exec('curl -s -f -L -H ' . escapeshellarg('Authorization: token ' . $token) . ' -o ' . escapeshellarg($tmp) . ' ' . escapeshellarg($url) . ' 2>&1', $output, $return_code); } - log::add('update', 'alert', $result); - + if ($return_code !== 0 || !file_exists($tmp) || filesize($tmp) == 0) { + $error_msg = __('Erreur lors du téléchargement', __FILE__); + if (!empty($output)) { + $error_msg .= ': ' . implode("\n", $output); + } + if (!file_exists($tmp)) { + $error_msg .= ' - ' . __('Fichier non créé', __FILE__); + } elseif (filesize($tmp) == 0) { + $error_msg .= ' - ' . __('Fichier vide', __FILE__); + } + log::add('update', 'error', $error_msg); + throw new Exception($error_msg); + } + log::add('update', 'info', __('Téléchargement réussi', __FILE__) . ' (' . round(filesize($tmp) / 1024 / 1024, 2) . ' MB)'); + if (!isset($branch['commit']) || !isset($branch['commit']['sha'])) { return array('path' => $tmp); } return array('localVersion' => $branch['commit']['sha'], 'path' => $tmp); } - + public static function deleteObjet($_update) { - } - + public static function objectInfo($_update) { return array( 'doc' => 'https://github.com/' . $_update->getConfiguration('user') . '/' . $_update->getConfiguration('repository') . '/blob/' . $_update->getConfiguration('version', 'master') . '/doc/' . config::byKey('language', 'core', 'fr_FR') . '/index.asciidoc', 'changelog' => 'https://github.com/' . $_update->getConfiguration('user') . '/' . $_update->getConfiguration('repository') . '/commits/' . $_update->getConfiguration('version', 'master'), ); } - + public static function downloadCore($_path) { $url = 'https://api.github.com/repos/' . config::byKey('github::core::user', 'core', 'jeedom') . '/' . config::byKey('github::core::repository', 'core', 'core') . '/zipball/' . config::byKey('github::core::branch', 'core', 'stable'); echo __('Téléchargement de', __FILE__) . ' ' . $url . '...'; @@ -174,15 +186,14 @@ public static function downloadCore($_path) { } return; } - + public static function versionCore() { $url = 'https://raw.githubusercontent.com/'.config::byKey('github::core::user', 'core', 'jeedom').'/'.config::byKey('github::core::repository', 'core', 'core').'/' . config::byKey('github::core::branch', 'core', 'stable') . '/core/config/version'; $request_http = new com_http($url); return trim($request_http->exec(30)); } - + /* * *********************Methode d'instance************************* */ - + /* * **********************Getteur Setteur*************************** */ - } diff --git a/core/repo/market.display.repo.php b/core/repo/market.display.repo.php index cb0aa4a257..551e03f3e6 100644 --- a/core/repo/market.display.repo.php +++ b/core/repo/market.display.repo.php @@ -114,21 +114,25 @@ ?> getCertification() == 'Premium') { - echo '{{Nous Contacter}}'; + echo '{{Nous Contacter}}'; } else { - if ($market->getCost() > 0) { - if ($market->getPurchase() == 1 && isset($purchase_info['user_id']) && is_numeric($purchase_info['user_id'])) { - echo '{{Plugin deja acheté et/ou inclus dans votre service Pack}}'; - }else{ - if ($market->getCost() != $market->getRealCost()) { + if (isset($purchase_info['user_id']) && is_numeric($purchase_info['user_id']) && $market->getPurchase() == 1) { + echo '{{Plugin deja acheté et/ou inclus dans votre service Pack}}'; + } else { + if ($market->getCost() > 0) { + if ($market->getCost() != $market->getRealCost()) { echo '' . number_format($market->getRealCost(), 2) . ' € '; - } - echo '' . number_format($market->getCost(), 2) . ' € TTC'; - } - } else { - echo '{{Gratuit}}'; - } + } + echo '' . number_format($market->getCost(), 2) . ' € TTC'; + } else { + echo '{{Gratuit}}'; + } + } } ?> diff --git a/core/repo/market.repo.php b/core/repo/market.repo.php index 97b7c08c33..d4a5de72bb 100644 --- a/core/repo/market.repo.php +++ b/core/repo/market.repo.php @@ -144,7 +144,7 @@ public static function pullInstall() { $update->setType($repo->getType()); $update->setLocalVersion($repo->getDatetime($plugin['version'])); $update->setConfiguration('version', $plugin['version']); - $update->setConfiguration('user',null); + $update->setConfiguration('user', null); $update->save(); $update->doUpdate(); $nbInstall++; @@ -231,31 +231,32 @@ public static function objectInfo($_update) { /* * ***********************BACKUP*************************** */ public static function backup_createFolderIsNotExist() { - $request_http = new com_http(config::byKey('service::backup::url').'/webdav/'.config::byKey('market::username'),config::byKey('market::username'),config::byKey('market::password')); + $request_http = new com_http(config::byKey('service::backup::url') . '/webdav/' . urlencode(config::byKey('market::username')), config::byKey('market::username'), config::byKey('market::password')); $request_http->setCURLOPT(array( - CURLOPT_CUSTOMREQUEST => "PROPFIND" + CURLOPT_CUSTOMREQUEST => "PROPFIND" )); $xml = simplexml_load_string($request_http->exec()); $ns = $xml->getNamespaces(true); $child = $xml->children($ns['D']); - $found = false; + $found = false; foreach ($child->response as $file) { - if($file->propstat->prop->getcontenttype){ + if ($file->propstat->prop->getcontenttype) { continue; } - $folder = trim(trim(str_replace('http://backup.jeedom.com/webdav/'.config::byKey('market::username'),'',$file->href),'/')); - if($folder == ''){ + $folder = trim(trim(str_replace('http://backup.jeedom.com/webdav/' . urlencode(config::byKey('market::username')), '', $file->href), '/')); + $folder = trim(trim(str_replace('http://backup.jeedom.com/webdav/' . config::byKey('market::username'), '', $folder), '/')); + if ($folder == '') { continue; } - if($folder == config::byKey('market::cloud::backup::name')){ + if ($folder == config::byKey('market::cloud::backup::name')) { $found = true; break; } } if (!$found) { - $request_http = new com_http(config::byKey('service::backup::url').'/webdav/'.config::byKey('market::username').'/'.rawurldecode(config::byKey('market::cloud::backup::name')),config::byKey('market::username'),config::byKey('market::password')); + $request_http = new com_http(config::byKey('service::backup::url') . '/webdav/' . urlencode(config::byKey('market::username')) . '/' . rawurldecode(config::byKey('market::cloud::backup::name')), config::byKey('market::username'), config::byKey('market::password')); $request_http->setCURLOPT(array( - CURLOPT_CUSTOMREQUEST => "MKCOL" + CURLOPT_CUSTOMREQUEST => "MKCOL" )); $request_http->exec(); } @@ -271,7 +272,7 @@ public static function backup_send($_path) { if (config::byKey('market::cloud::backup::password') != config::byKey('market::cloud::backup::password_confirmation')) { throw new Exception(__('Le mot de passe du backup cloud n\'est pas identique à la confirmation', __FILE__)); } - self::backup_clean($_path); + //self::backup_clean($_path); self::backup_createFolderIsNotExist(); try { if (!file_exists('/tmp/jeedom_gnupg')) { @@ -280,8 +281,13 @@ public static function backup_send($_path) { com_shell::execute('sudo chmod 777 -R /tmp/jeedom_gnupg'); $cmd = 'echo "' . config::byKey('market::cloud::backup::password') . '" | gpg --homedir /tmp/jeedom_gnupg --batch --yes --passphrase-fd 0 -c ' . $_path; com_shell::execute($cmd); - $cmd = "curl --user '".config::byKey('market::username').":".config::byKey('market::password')."' -T '".$_path . '.gpg'."' '".config::byKey('service::backup::url').'/webdav/'.config::byKey('market::username'). '/' . rawurldecode(config::byKey('market::cloud::backup::name'))."/'"; - com_shell::execute($cmd); + $cmd = "curl --retry 5 --retry-delay 3 --retry-max-time 60 --retry-connrefused --keepalive-time 30 --user '" . config::byKey('market::username') . ":" . config::byKey('market::password') . "' -T '" . $_path . '.gpg' . "' '" . config::byKey('service::backup::url') . '/webdav/' . urlencode(config::byKey('market::username')) . '/' . rawurldecode(config::byKey('market::cloud::backup::name')) . "/'"; + try { + com_shell::execute($cmd); + } catch (\Exception $e) { + $cmd = "curl --http1.1 --retry 5 --retry-delay 3 --retry-max-time 60 --retry-connrefused --keepalive-time 30 --user '" . config::byKey('market::username') . ":" . config::byKey('market::password') . "' -T '" . $_path . '.gpg' . "' '" . config::byKey('service::backup::url') . '/webdav/' . urlencode(config::byKey('market::username')) . '/' . rawurldecode(config::byKey('market::cloud::backup::name')) . "/'"; + com_shell::execute($cmd); + } unlink($_path . '.gpg'); rrmdir('/tmp/jeedom_gnupg'); } catch (\Exception $e) { @@ -298,17 +304,17 @@ public static function backup_clean($_path) { $limit = 3700; self::backup_createFolderIsNotExist(); - $request_http = new com_http(config::byKey('service::backup::url').'/webdav/'.config::byKey('market::username'),config::byKey('market::username'),config::byKey('market::password')); + $request_http = new com_http(config::byKey('service::backup::url') . '/webdav/' . urlencode(config::byKey('market::username')), config::byKey('market::username'), config::byKey('market::password')); $request_http->setCURLOPT(array( - CURLOPT_CUSTOMREQUEST => "PROPFIND" + CURLOPT_CUSTOMREQUEST => "PROPFIND" )); $xml = simplexml_load_string($request_http->exec()); $ns = $xml->getNamespaces(true); $child = $xml->children($ns['D']); $total_size = 0; - $files = []; + $files = []; foreach ($child->response as $file) { - if(!$file->propstat->prop->getcontenttype){ + if (!$file->propstat->prop->getcontenttype) { continue; } $files[] = array( @@ -335,9 +341,9 @@ public static function backup_clean($_path) { } $file = array_shift($files); echo __('Supression du backup cloud :', __FILE__) . ' ' . $file['name'] . "\n"; - $request_http = new com_http($file['href'],config::byKey('market::username'),config::byKey('market::password')); + $request_http = new com_http($file['href'], config::byKey('market::username'), config::byKey('market::password')); $request_http->setCURLOPT(array( - CURLOPT_CUSTOMREQUEST => "DELETE" + CURLOPT_CUSTOMREQUEST => "DELETE" )); $request_http->exec(); $total_size -= $file['size']; @@ -353,24 +359,24 @@ public static function backup_list() { return array(); } self::backup_createFolderIsNotExist(); - $request_http = new com_http(config::byKey('service::backup::url').'/webdav/'.config::byKey('market::username'). '/' . rawurldecode(config::byKey('market::cloud::backup::name')),config::byKey('market::username'),config::byKey('market::password')); + $request_http = new com_http(config::byKey('service::backup::url') . '/webdav/' . urlencode(config::byKey('market::username')) . '/' . rawurldecode(config::byKey('market::cloud::backup::name')), config::byKey('market::username'), config::byKey('market::password')); $request_http->setCURLOPT(array( - CURLOPT_CUSTOMREQUEST => "PROPFIND" + CURLOPT_CUSTOMREQUEST => "PROPFIND" )); $xml = simplexml_load_string($request_http->exec()); $ns = $xml->getNamespaces(true); $child = $xml->children($ns['D']); $files = array(); foreach ($child->response as $file) { - if(!$file->propstat->prop->getcontenttype){ + if (!$file->propstat->prop->getcontenttype) { continue; } $files[] = array( - 'name' => (string) $file->propstat->prop->displayname, - 'timestamp' => strtotime($file->propstat->prop->getlastmodified) - ); + 'name' => (string) $file->propstat->prop->displayname, + 'timestamp' => strtotime($file->propstat->prop->getlastmodified) + ); } - function cmp($a,$b){ // $a,$b are reference to first index of array + function cmp($a, $b) { // $a,$b are reference to first index of array return strcmp($a["timestamp"], $b["timestamp"]); } usort($files, "cmp"); @@ -411,6 +417,9 @@ public static function backup_restore($_backup) { /* * ***********************CRON*************************** */ public static function cronHourly() { + if (empty(config::byKey('market::username')) || empty(config::byKey('market::password'))) { + return; + } if (strtotime(config::byKey('market::lastCommunication', 'core', 0)) > (strtotime('now') - (24 * 3600))) { return; } @@ -441,7 +450,7 @@ public static function cron5() { try { $result = json_decode($request_http->exec(60, 1), true); if ($result == null || $result['state'] != 'ok') { - sleep(rand(5,45)); + sleep(rand(5, 45)); $result = json_decode($request_http->exec(60, 1), true); } if ($result == null || $result['state'] != 'ok') { @@ -957,13 +966,13 @@ public function install($_version = 'stable') { } $url = config::byKey('market::address') . "/core/php/downloadFile.php?id=" . $this->getId(); - $url .='&version=' . $_version ; - $url .='&jeedomversion=' . jeedom::version(); - $url .='&osversion=' . system::getOsVersion(); - $url .='&hwkey=' . jeedom::getHardwareKey(); - $url .='&username=' . urlencode(config::byKey('market::username')); - $url .='&password=' . self::getPassword(); - $url .='&password_type=sha1'; + $url .= '&version=' . $_version; + $url .= '&jeedomversion=' . jeedom::version(); + $url .= '&osversion=' . system::getOsVersion(); + $url .= '&hwkey=' . jeedom::getHardwareKey(); + $url .= '&username=' . urlencode(config::byKey('market::username')); + $url .= '&password=' . self::getPassword(); + $url .= '&password_type=sha1'; log::add('update', 'alert', __('Téléchargement de', __FILE__) . ' ' . $this->getLogicalId() . '...'); log::add('update', 'alert', __('URL', __FILE__) . ' ' . $url); exec('wget "' . $url . '" -O ' . $tmp . ' >> ' . log::getPathToLog('update') . ' 2>&1'); @@ -1065,7 +1074,7 @@ public function save() { } if ($update->getSource() == 'market') { $update->setConfiguration('version', 'beta'); - $update->setLocalVersion(date('Y-m-d H:i:s',(int) strtotime('+10 minute' . date('Y-m-d H:i:s')))); + $update->setLocalVersion(date('Y-m-d H:i:s', (int) strtotime('+10 minute' . date('Y-m-d H:i:s')))); $update->save(); } $update->checkUpdate(); diff --git a/core/template/dashboard/cmd.action.color.picker.html b/core/template/dashboard/cmd.action.color.picker.html index 3c4bf73cca..62d1f17f5a 100644 --- a/core/template/dashboard/cmd.action.color.picker.html +++ b/core/template/dashboard/cmd.action.color.picker.html @@ -19,7 +19,13 @@ if ('#time#' == 'duration' || '#time#' == 'date') { jeedom.cmd.displayDuration(_options.valueDate, cmd.querySelector('.timeCmd'), '#time#') } - cmd.querySelector('.action_colorpicker_change').value = (_options.display_value != '') ? _options.display_value.substr(0, 7) : '#000000' + if (_options.display_value.length == 0) { + cmd.querySelector('.action_colorpicker_change').value = '#000000' + } else if (_options.display_value.startsWith('#')) { + cmd.querySelector('.action_colorpicker_change').value = _options.display_value.substr(0, 7) + } else { + cmd.querySelector('.action_colorpicker_change').value = '#' + _options.display_value.substr(0, 6) + } } }) diff --git a/desktop/js/scenario.js b/desktop/js/scenario.js index 531d350dcb..ab6f03f5b5 100644 --- a/desktop/js/scenario.js +++ b/desktop/js/scenario.js @@ -1999,8 +1999,11 @@ document.getElementById('div_editScenario').querySelector('div.floatingbar').add _target.setAttribute('data-state', '1') //open code blocks for later search: document.querySelectorAll('#div_scenarioElement div.elementCODE.elementCollapse').forEach(_code => { - _code.removeClass('elementCollapse') - _code.querySelector('textarea[data-l1key="expression"]').show() + _code.classList.remove('elementCollapse'); + const textarea = _code.querySelector('textarea[data-l1key="expression"]'); + if (textarea) { + textarea.style.display = 'block'; + } }) jeeP.setEditors() document.querySelectorAll('textarea[data-l1key="expression"]').unseen() diff --git a/desktop/modal/search.php b/desktop/modal/search.php index 051940aa03..62b4635a10 100644 --- a/desktop/modal/search.php +++ b/desktop/modal/search.php @@ -75,7 +75,7 @@ - + @@ -652,6 +652,23 @@ jeeM.init() + function updateEquipmentInputDisabled() { + var inputIds = [ + 'in_searchFor_equipment', + 'in_searchFor_command', + ]; + inputIds.forEach(function(id) { + var input = document.getElementById(id); + if (input) { + input.disabled = true; + } + }); + } + + updateEquipmentInputDisabled(); + + + //Manage events outside parents delegations: document.querySelector('#md_search #in_searchFor_string')?.addEventListener('keypress', function(event) { if (event.which === 13) { @@ -785,6 +802,8 @@ if (dataFilter[i] == 1) table.parentNode.seen() else table.parentNode.unseen() } + + updateEquipmentInputDisabled(); return } diff --git a/desktop/php/administration.php b/desktop/php/administration.php index e89a5e1f0d..f226834255 100644 --- a/desktop/php/administration.php +++ b/desktop/php/administration.php @@ -41,7 +41,7 @@ {{Rapports}} {{Liens}} {{Interactions}} - {{Securité}} + {{Sécurité}} {{Mises à jour/Market}} {{Cache}} {{API}} @@ -950,6 +950,12 @@ + + {{Taille maximale pour fichier de log (en Mo)}} + + + + {{Niveau de log par défaut}} @@ -1128,15 +1134,15 @@ {{Limiter à une valeur toutes les}} - + - - {{Aucun}} - {{1 min}} - {{5 min}} - {{10 min}} - + + {{Aucun}} + {{1 min}} + {{5 min}} + {{10 min}} + @@ -1761,11 +1767,11 @@ foreach ($ban_ips as $ip => $datetime) { $div .= ''; $div .= '' . $ip . ''; - $div .= '' . date('Y-m-d H:i:s',(int) $datetime) . ''; + $div .= '' . date('Y-m-d H:i:s', (int) $datetime) . ''; if (config::byKey('security::bantime') == -1) { $div .= '{{Jamais}}'; } else { - $div .= '' . date('Y-m-d H:i:s',(int) ($datetime + config::byKey('security::bantime'))) . ''; + $div .= '' . date('Y-m-d H:i:s', (int) ($datetime + config::byKey('security::bantime'))) . ''; } $div .= ''; } @@ -1814,52 +1820,52 @@ {{Stable}} - getValue(array()); - if(!isset($lists['branchs']) || !is_array($lists['branchs'])){ + if (!isset($lists['branchs']) || !is_array($lists['branchs'])) { $request_http = new com_http('https://api.github.com/repos/jeedom/core/branches'); $request_http->setHeader(array('User-agent: jeedom')); try { $lists['branchs'] = json_decode($request_http->exec(10, 1), true); } catch (\Exception $e) { } - cache::set('core::branch::default::list',$lists,86400); + cache::set('core::branch::default::list', $lists, 86400); } - if(!isset($lists['tags']) || !is_array($lists['tags'])){ + if (!isset($lists['tags']) || !is_array($lists['tags'])) { $request_http = new com_http('https://api.github.com/repos/jeedom/core/tags'); $request_http->setHeader(array('User-agent: jeedom')); try { $lists['tags'] = json_decode($request_http->exec(10, 1), true); } catch (\Exception $e) { } - cache::set('core::branch::default::list',$lists,86400); - } - if(isset($lists['branchs']) && is_array($lists['branchs'])){ - echo ''; - foreach ($lists['branchs'] as $branch) { - if(!is_array($branch) || !isset($branch['name'])){ - continue; - } - if(in_array($branch['name'],array('V4-stable','master'))){ - continue; + cache::set('core::branch::default::list', $lists, 86400); + } + if (isset($lists['branchs']) && is_array($lists['branchs'])) { + echo ''; + foreach ($lists['branchs'] as $branch) { + if (!is_array($branch) || !isset($branch['name'])) { + continue; + } + if (in_array($branch['name'], array('V4-stable', 'master'))) { + continue; + } + echo '' . $branch['name'] . ''; } - echo ''.$branch['name'].''; + echo ''; } - echo ''; - } - if(isset($lists['tags']) && is_array($lists['tags'])){ - echo ''; - foreach ($lists['tags'] as $tag) { - if(!is_array($tag) || !isset($tag['name'])){ - continue; + if (isset($lists['tags']) && is_array($lists['tags'])) { + echo ''; + foreach ($lists['tags'] as $tag) { + if (!is_array($tag) || !isset($tag['name'])) { + continue; + } + echo '' . $tag['name'] . ''; } - echo ''.$tag['name'].''; + echo ''; } - echo ''; } - } - ?> + ?> @@ -2331,4 +2337,4 @@ - + \ No newline at end of file diff --git a/desktop/php/object.php b/desktop/php/object.php index 0db55bcc10..87ce627fbf 100644 --- a/desktop/php/object.php +++ b/desktop/php/object.php @@ -3,7 +3,7 @@ throw new Exception('{{401 - Accès non autorisé}}'); } $allObject = jeeObject::buildTree(null, false); -$config_objSummary = config::byKey('object:summary'); +$config_objSummary = (array) config::byKey('object:summary'); sendVarToJS([ 'jeephp2js.selectId' => init('id', '-1'), @@ -102,7 +102,9 @@ - {{Nom à afficher (si vide alors le nom de l'objet sera utilisé)}} + {{Nom à afficher}} + + diff --git a/desktop/php/update.php b/desktop/php/update.php index 5d7295083a..ea18cb41bd 100644 --- a/desktop/php/update.php +++ b/desktop/php/update.php @@ -185,7 +185,7 @@ {{Script d'update à réappliquer}} - {{Aucune}} + {{Aucun}} removeEvent($_id)` @@ -10,11 +31,12 @@ - Les graphiques se mettent à jour automatiquement lors de l'arrivée de nouvelles valeurs [LIEN](https://github.com/jeedom/core/issues/2749) - Jeedom ajoute automatiquement la hauteur de l'image lors de la création des widgets pour éviter les soucis de chevauchement en mobile [LIEN](https://github.com/jeedom/core/issues/2539) - Refonte de la partie backup cloud [LIEN](https://github.com/jeedom/core/issues/2765) -- **DEV** Mise en place d'un système de queue pour l'exécution d'actions [LIEN](https://github.com/jeedom/core/issues/2489) +- [Développeurs] Mise en place d'un système de queue pour l'exécution d'actions [LIEN](https://github.com/jeedom/core/issues/2489) - Les tags des scénarios sont maintenant propres à l'instance du scénario (si vous avez deux lancements de scénarios très proches, les tags du dernier n'écrasent plus le premier) [LIEN](https://github.com/jeedom/core/issues/2763) - Changement sur la partie trigger des scénarios : [LIEN](https://github.com/jeedom/core/issues/2414) - ``triggerId()`` est maintenant deprecated et sera retiré dans les futures mises à jour du core. Si vous avez ``triggerId() == 587`` il faut le remplacer par ``#trigger_id# == 587`` - ``triggerValue()`` est maintenant deprecated et sera retiré dans les futures mises à jour du core. Si vous avez ``triggerValue() == 10`` il faut le remplacer par ``#trigger_value# == 10`` + - ``trigger()`` est maintenant deprecated et sera retiré dans les futures mises à jour du core. Si vous avez ``trigger(#[objet][equipement][commande]#)`` il faut le remplacer par ``#trigger_name# == '[objet][equipement][commande]'`` - ``#trigger#`` : Peut être : - ``api`` si le lancement a été déclenché par l'API, - ``TYPEcmd`` si le lancement a été déclenché par une commande, avec TYPE remplacé par l'id du plugin (ex virtualCmd), @@ -22,7 +44,7 @@ - ``user`` s'il a été lancé manuellement, - ``start`` pour un lancement au démarrage de Jeedom. - ``#trigger_id#`` : Si c'est une commande qui a déclenché le scénario alors ce tag prend la valeur de l'id de la commande qui l'a déclenché - - ``#trigger_name#`` : Si c'est une commande qui a déclenché le scénario alors ce tag prend la valeur du nom de la commande (sous forme [objet][équipement][commande]) + - ``#trigger_name#`` : Si c'est une commande qui a déclenché le scénario alors ce tag prend la valeur du nom de la commande (sous forme '[objet][équipement][commande]'). Notez qu'en utilisant la syntaxe : ``#trigger_name# == '[objet][equipement][commande]'``, en cas de modification de nom de votre objet ou équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code. - ``#trigger_value#`` : Si c'est une commande qui a déclenché le scénario alors ce tag prend la valeur de la commande ayant déclenché le scénario - ``#trigger_message#`` : Message indiquant l'origine du lancement du scénario - Amélioration de la gestion des plugins sur github (plus de dépendances à une librairie tierce) [LIEN](https://github.com/jeedom/core/issues/2567) @@ -95,4 +117,4 @@ >**IMPORTANT** > -> La restauration d'un backup 4.4 peut dans certains cas finir par des erreurs dans l'interface web. Rien de grave cela peut facilement se corriger il suffit de faire : `cd /tmp;wget https://github.com/jeedom/core/archive/refs/tags/4.4.19.zip;unzip 4.4.19.zip;cd core-4.4.19;cp -rf * /var/www/html/;rm -rf /tmp/master.zip;rm -rf /tmp/core-4.4.19;`. Vous pouvez lancer cette commande depuis l'interface rescue de jeedom (ajouter `&rescue=1` dans l'url), ou directement en ssh. +> La restauration d'un backup 4.4 peut dans certains cas finir par des erreurs dans l'interface web. Rien de grave cela peut facilement se corriger il suffit de faire : `cd /tmp;wget https://github.com/jeedom/core/archive/refs/tags/4.4.20.zip;unzip 4.4.20.zip;cd core-4.4.20;cp -rf * /var/www/html/;rm -rf /tmp/master.zip;rm -rf /tmp/core-4.4.20;`. Vous pouvez lancer cette commande depuis l'interface rescue de jeedom (ajouter `&rescue=1` dans l'url), ou directement en ssh. diff --git a/docs/fr_FR/history.md b/docs/fr_FR/history.md index 7930e65b5b..6322cb45b1 100644 --- a/docs/fr_FR/history.md +++ b/docs/fr_FR/history.md @@ -93,10 +93,8 @@ Vous avez aussi accès à une gestion de formules de calcul qui vous permet de l #### Historique de commande -Devant chaque donnée pouvant être affichée, vous retrouvez deux icônes : - -- **Poubelle** : Permet de supprimer les données enregistrées ; lors du clic, Jeedom demande s’il faut supprimer les données avant une certaine date ou toutes les données. -- **Flèche** : Permet d’avoir un export CSV des données historisées. +- Devant chaque donnée pouvant être affichée, vous retrouvez une icône **Poubelle** qui permet de supprimer les données enregistrées ; lors du clic, Jeedom demande s’il faut supprimer les données avant une certaine date ou toutes les données. +- Dans **Configuration** vous trouvez à droite de chaque donnée, une icône **Flèche** qui permet d’avoir un export CSV des données historisées. ### Suppression de valeur incohérente diff --git a/docs/fr_FR/scenario.md b/docs/fr_FR/scenario.md index e002eafb7c..256d54852b 100644 --- a/docs/fr_FR/scenario.md +++ b/docs/fr_FR/scenario.md @@ -236,7 +236,7 @@ Il existe des déclencheurs spécifiques (autre que ceux fournis par les command - ``#end_update#`` : Événement envoyé à la fin d’une mise à jour. - ``#begin_restore#`` : Événement envoyé au début d’une restauration. - ``#end_restore#`` : Événement envoyé à la fin d’une restauration. -- ``#user_connect#`` : Connexion d'un utilisateur +- ``#user_connect#`` : Connexion d'un utilisateur, le tag `#trigger_value#` contient le nom de l'utilisateur. - ``#variable(nom_variable)#`` : Changement de valeur de la variable nom_variable. - ``#genericType(GENERIC, #[Object]#)#`` : Changement d'une commande info de Type Generic GENERIC, dans l'objet Object. - ``#new_eqLogic#`` : Événement envoyé lors de la création d'un nouvelle équipement, vous avez dans les tags id (id de l'équipement crée), name (nom de l'équipement crée) et eqType (type/plugin de l'équipement crée) @@ -297,7 +297,7 @@ Un tag est remplacé lors de l’exécution du scénario par sa valeur. Vous pou - ``user`` s'il a été lancé manuellement, - ``start`` pour un lancement au démarrage de Jeedom. - ``#trigger_id#`` : Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de l'id de la commande qui l'a déclenché. Exemple : ``#trigger_id# == 19`` -- ``#trigger_name#`` : Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande]). Exemple : ``#trigger_name# == '[cuisine][lumiere][etat]'`` +- ``#trigger_name#`` : Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande]). Exemple : ``#trigger_name# == '[cuisine][lumiere][etat]'``. Notez qu'en utilisant la syntaxe : ``#trigger_name# == '[cuisine][lumiere][etat]'``, en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code. - ``#trigger_value#`` : Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de la commande ayant déclenché le scénario. Astuce si vous voulez la valeur courante de la commande qui a déclencher le scénario (et non sa valeur au déclenchement) vous pouvez utiliser : ``##trigger_id##`` (double #) - ``#latitude#`` : Permet de récuperer l'information de latitude mise dans la configuration de jeedom - ``#longitude#`` : Permet de récuperer l'information de longitude mise dans la configuration de jeedom @@ -439,7 +439,7 @@ Une boîte à outils de fonctions génériques peut également servir à effectu - ``randText(texte1;texte2;texte…..)`` : Permet de retourner un des textes aléatoirement (séparer les texte par un ; ). Il n’y a pas de limite dans le nombre de texte. - ``randomColor(min,max)`` : Donne une couleur aléatoire comprise entre 2 bornes ( 0 => rouge, 50 => vert, 100 => bleu). - ``trigger(commande)`` : Permet de connaître le déclencheur du scénario ou de savoir si c’est bien la commande passée en paramètre qui a déclenché le scénario. **=> Deprecated il vaut mieux utiliser le tag #trigger#** -- ``triggerValue()`` : Permet de connaître la valeur du déclencheur du scénario. **=> Deprecated il vaut mieux utiliser le tag #triggerValue#** +- ``triggerValue()`` : Permet de connaître la valeur du déclencheur du scénario. **=> Deprecated il vaut mieux utiliser le tag #trigger_value#** - ``round(valeur,[decimal])`` : Donne un arrondi au-dessus, [decimal] nombre de décimales après la virgule. - ``odd(valeur)`` : Permet de savoir si un nombre est impair ou non. Renvoie 1 si impair 0 sinon. - ``median(commande1,commande2….commandeN)`` : Renvoie la médiane des valeurs. diff --git a/docs/fr_FR/static-analysis/phpstan.md b/docs/fr_FR/static-analysis/phpstan.md index 91788ab33b..f1fe46defa 100644 --- a/docs/fr_FR/static-analysis/phpstan.md +++ b/docs/fr_FR/static-analysis/phpstan.md @@ -2,12 +2,7 @@ ## Installation locale -1. Télécharger PHPStan : -```bash -wget https://github.com/phpstan/phpstan/releases/latest/download/phpstan.phar -``` - -2. Mettre à jour les dépendances : +Mettre à jour les dépendances : ```bash composer update --ignore-platform-reqs ``` @@ -43,7 +38,7 @@ Notes importantes : ### Lancer l'analyse ```bash -php phpstan.phar analyse --configuration phpstan.neon +vendor/bin/phpstan analyse --configuration phpstan.neon ``` ### Types d'erreurs courantes et solutions @@ -102,7 +97,7 @@ $resultat = codeProblematiqueQuiNeDoitPasEtreModifie(); ### Générer un nouveau baseline Si de nombreuses erreurs existantes doivent être ignorées : ```bash -php phpstan.phar analyse --configuration phpstan.neon --generate-baseline +vendor/bin/phpstan analyse --configuration phpstan.neon --generate-baseline ``` ## Intégration continue @@ -139,4 +134,4 @@ Un processus automatique a été mis en place pour maintenir le baseline à jour --- -Besoin d'aide supplémentaire ? Consultez la [documentation officielle de PHPStan](https://phpstan.org/user-guide/getting-started). \ No newline at end of file +Besoin d'aide supplémentaire ? Consultez la [documentation officielle de PHPStan](https://phpstan.org/user-guide/getting-started). diff --git a/docs/i18n/de_DE.json b/docs/i18n/de_DE.json index 798e06ef2a..dd56ced7de 100644 --- a/docs/i18n/de_DE.json +++ b/docs/i18n/de_DE.json @@ -6884,6 +6884,8 @@ "pour un lancement au démarrage de Jeedom": "für einen Start beim Start von Jeedom", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de l'id de la commande qui l'a déclenché": "Wenn es sich um einen Befehl handelt, der das Szenario ausgelöst hat, hat dieses Tag den Wert der ID des Befehls, der es ausgelöst hat", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande": "Wenn es sich um einen Befehl handelt, der das Szenario ausgelöst hat, hat dieses Tag den Wert des Namens des Befehls (in der Form [Objekt][Ausrüstung][Befehl", + "Notez qu'en utilisant la syntaxe": "Notez qu'en utilisant la syntaxe", + "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code": "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de la commande ayant déclenché le scénario": "Wenn es sich um einen Befehl handelt, der das Szenario ausgelöst hat, hat dieses Tag den Wert des Befehls, der das Szenario ausgelöst hat", "Astuce si vous voulez la valeur courante de la commande qui a déclencher le scénario (et non sa valeur au déclenchement) vous pouvez utiliser": "Tipp: Wenn Sie den aktuellen Wert des Befehls möchten, der das Szenario ausgelöst hat (und nicht seinen Wert zum Zeitpunkt der Auslösung), können Sie diesen verwenden", "Permet de récuperer l'information de latitude mise dans la configuration de jeedom": "Ermöglicht Ihnen, die in der Jeedom-Konfiguration eingegebenen Breitengradinformationen abzurufen", diff --git a/docs/i18n/en_US.json b/docs/i18n/en_US.json index f94a0eb74c..fcac739e4f 100644 --- a/docs/i18n/en_US.json +++ b/docs/i18n/en_US.json @@ -6884,6 +6884,8 @@ "pour un lancement au démarrage de Jeedom": "for a launch at startup of Jeedom", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de l'id de la commande qui l'a déclenché": "If it is a command which triggered the scenario then this tag has the value of the id of the command which triggered it", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande": "If it is a command which triggered the scenario then this tag has the value of the name of the command (in the form [object][equipment][command", + "Notez qu'en utilisant la syntaxe": "Notez qu'en utilisant la syntaxe", + "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code": "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de la commande ayant déclenché le scénario": "If it is a command which triggered the scenario then this tag has the value of the command which triggered the scenario", "Astuce si vous voulez la valeur courante de la commande qui a déclencher le scénario (et non sa valeur au déclenchement) vous pouvez utiliser": "Tip if you want the current value of the command which triggered the scenario (and not its value at triggering) you can use", "Permet de récuperer l'information de latitude mise dans la configuration de jeedom": "Allows you to retrieve the latitude information put in the jeedom configuration", diff --git a/docs/i18n/es_ES.json b/docs/i18n/es_ES.json index 34245f33b3..92094c55a1 100644 --- a/docs/i18n/es_ES.json +++ b/docs/i18n/es_ES.json @@ -6884,6 +6884,8 @@ "pour un lancement au démarrage de Jeedom": "para un lanzamiento al inicio de Jeedom", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de l'id de la commande qui l'a déclenché": "Si es un comando el que desencadenó el escenario, entonces esta etiqueta tiene el valor de la identificación del comando que lo desencadenó", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande": "Si es un comando el que desencadenó el escenario, entonces esta etiqueta tiene el valor del nombre del comando (en el formato [objeto][equipo][comando", + "Notez qu'en utilisant la syntaxe": "Notez qu'en utilisant la syntaxe", + "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code": "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de la commande ayant déclenché le scénario": "Si es un comando que desencadenó el escenario, entonces esta etiqueta tiene el valor del comando que desencadenó el escenario", "Astuce si vous voulez la valeur courante de la commande qui a déclencher le scénario (et non sa valeur au déclenchement) vous pouvez utiliser": "Consejo: si desea conocer el valor actual del comando que desencadenó el escenario (y no su valor en el momento de la activación), puede utilizar", "Permet de récuperer l'information de latitude mise dans la configuration de jeedom": "Le permite recuperar la información de latitud ingresada en la configuración de jeedom", diff --git a/docs/i18n/fr_FR.json b/docs/i18n/fr_FR.json index 5ebef45b8d..93c4972a5a 100644 --- a/docs/i18n/fr_FR.json +++ b/docs/i18n/fr_FR.json @@ -3845,6 +3845,8 @@ "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de l'id de la commande qui l'a déclenché": "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de l'id de la commande qui l'a déclenché", "Exemple": "Exemple", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande": "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande", + "Notez qu'en utilisant la syntaxe": "Notez qu'en utilisant la syntaxe", + "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code": "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de la commande ayant déclenché le scénario": "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de la commande ayant déclenché le scénario", "Astuce si vous voulez la valeur courante de la commande qui a déclencher le scénario (et non sa valeur au déclenchement) vous pouvez utiliser": "Astuce si vous voulez la valeur courante de la commande qui a déclencher le scénario (et non sa valeur au déclenchement) vous pouvez utiliser", "double": "double", diff --git a/docs/i18n/pt_PT.json b/docs/i18n/pt_PT.json index 77445125fd..75dffc74d3 100644 --- a/docs/i18n/pt_PT.json +++ b/docs/i18n/pt_PT.json @@ -6884,6 +6884,8 @@ "pour un lancement au démarrage de Jeedom": "para um lançamento na inicialização do Jeedom", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de l'id de la commande qui l'a déclenché": "Se for um comando que desencadeou o cenário então esta tag tem o valor do id do comando que o desencadeou", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur du nom de la commande (sous forme [objet][equipement][commande": "Se for um comando que disparou o cenário então esta tag terá o valor do nome do comando (na forma [objeto][equipamento][comando", + "Notez qu'en utilisant la syntaxe": "Notez qu'en utilisant la syntaxe", + "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code": "en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code", "Si c'est une commande qui a déclenché le scénario alors ce tag à la valeur de la commande ayant déclenché le scénario": "Se for um comando que acionou o cenário então esta tag terá o valor do comando que acionou o cenário", "Astuce si vous voulez la valeur courante de la commande qui a déclencher le scénario (et non sa valeur au déclenchement) vous pouvez utiliser": "Dica se você quiser o valor atual do comando que disparou o cenário (e não seu valor no disparo), você pode usar", "Permet de récuperer l'information de latitude mise dans la configuration de jeedom": "Permite recuperar as informações de latitude colocadas na configuração do jeedom", diff --git a/docs/pt_PT/scenario.md b/docs/pt_PT/scenario.md index 4482cad03c..c3583ad1be 100644 --- a/docs/pt_PT/scenario.md +++ b/docs/pt_PT/scenario.md @@ -297,7 +297,7 @@ Uma tag é substituída durante a execução do cenário por seu valor. Você po - ``user`` se foi iniciado manualmente, - ``start`` para um lançamento na inicialização do Jeedom. - ``#trigger_id#`` : Se for um comando que desencadeou o cenário então esta tag tem o valor do id do comando que o desencadeou. Exemplo : ``#trigger_id# == 19`` -- ``#trigger_name#`` : Se for um comando que disparou o cenário então esta tag terá o valor do nome do comando (na forma [objeto][equipamento][comando]). Exemplo : ``#trigger_name# == '[cuisine][lumiere][etat]'`` +- ``#trigger_name#`` : Se for um comando que disparou o cenário então esta tag terá o valor do nome do comando (na forma [objeto][equipamento][comando]). Exemplo : ``#trigger_name# == '[cuisine][lumiere][etat]'``. Notez qu'en utilisant la syntaxe : ``#trigger_name# == '[cuisine][lumiere][etat]'``, en cas de modification de nom de votre objet, équipement ou commande, ce ne sera pas mis à jour automatiquement dans votre code. - ``#trigger_value#`` : Se for um comando que acionou o cenário então esta tag terá o valor do comando que acionou o cenário. Dica se você quiser o valor atual do comando que disparou o cenário (e não seu valor no disparo), você pode usar : ``##trigger_id##`` (dobro #) - ``#latitude#`` : Permite recuperar as informações de latitude colocadas na configuração do jeedom - ``#longitude#`` : Permite recuperar as informações de longitude colocadas na configuração do jeedom diff --git a/install/OS_specific/Docker/init.sh b/install/OS_specific/Docker/init.sh index 1bb9c50f77..8bfe14f32b 100644 --- a/install/OS_specific/Docker/init.sh +++ b/install/OS_specific/Docker/init.sh @@ -81,10 +81,15 @@ if [ -f ${WEBSERVER_HOME}/core/config/common.config.php ]; then else echo 'Start jeedom installation' JEEDOM_INSTALL=0 - rm -rf /root/install.sh - wget https://raw.githubusercontent.com/jeedom/core/${VERSION}/install/install.sh -O /root/install.sh - chmod +x /root/install.sh - /root/install.sh -s 6 -v ${VERSION} -w ${WEBSERVER_HOME} + + # do not re-install jeedom + if [ ! -f ${WEBSERVER_HOME}/core/config/common.config.sample.php ]; then + echo 'download again Jeedom' + /root/install.sh -s 6 -v ${VERSION} -w ${WEBSERVER_HOME} + # jeedom installation : install composer + /root/install.sh -s 10 -v ${VERSION} -w ${WEBSERVER_HOME} -i docker + fi + if [ $(which mysqld | wc -l) -ne 0 ]; then chown -R mysql:mysql /var/lib/mysql mysql_install_db --user=mysql --basedir=/usr/ --ldata=/var/lib/mysql/ diff --git a/install/backup.php b/install/backup.php index 00efd4f7d0..96c83baa1e 100644 --- a/install/backup.php +++ b/install/backup.php @@ -146,6 +146,7 @@ 'python_venv', 'resources/venv', '/vendor', + 'install/update/*.img.gz', config::byKey('backup::path'), ); diff --git a/install/consistency.php b/install/consistency.php index 3d3907538a..7434197e0c 100644 --- a/install/consistency.php +++ b/install/consistency.php @@ -128,333 +128,335 @@ if (is_object($cron)) { $cron->remove(); } - if (method_exists('utils', 'attrChanged')) { - $cron = cron::byClassAndFunction('plugin', 'cronDaily'); - if (!is_object($cron)) { - echo "Create plugin::cronDaily\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('cronDaily'); - $cron->setSchedule('00 00 * * *'); - $cron->setTimeout(240); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('jeedom', 'backup'); - if (!is_object($cron)) { - echo "Create jeedom::backup\n"; - $cron = new cron(); - $cron->setClass('jeedom'); - $cron->setFunction('backup'); - $cron->setSchedule(rand(10, 59) . ' 0' . rand(0, 7) . ' * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(60); - $cron->save(); - } - - $cron = cron::byClassAndFunction('plugin', 'cronHourly'); - if (!is_object($cron)) { - echo "Create plugin::cronHourly\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('cronHourly'); - $cron->setSchedule('00 * * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(60); - $cron->save(); - - $cron = cron::byClassAndFunction('scenario', 'check'); - if (!is_object($cron)) { - echo "Create scenario::check\n"; - $cron = new cron(); - } - $cron->setClass('scenario'); - $cron->setFunction('check'); - $cron->setSchedule('* * * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(30); - $cron->save(); - - $cron = cron::byClassAndFunction('scenario', 'control'); - if (!is_object($cron)) { - echo "Create scenario::control\n"; - $cron = new cron(); - } - $cron->setClass('scenario'); - $cron->setFunction('control'); - $cron->setSchedule('* * * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(30); - $cron->save(); - - $cron = cron::byClassAndFunction('jeedom', 'cronDaily'); - if (!is_object($cron)) { - echo "Create jeedom::cronDaily\n"; - $cron = new cron(); - } - $cron->setClass('jeedom'); - $cron->setFunction('cronDaily'); - $cron->setSchedule(rand(0, 59) . ' ' . rand(0, 3) . ' * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(240); - $cron->save(); - - $cron = cron::byClassAndFunction('jeedom', 'cronHourly'); - if (!is_object($cron)) { - echo "Create jeedom::cronHourly\n"; - $cron = new cron(); - } - $cron->setClass('jeedom'); - $cron->setFunction('cronHourly'); - $cron->setSchedule(rand(0, 59) . ' * * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(60); - $cron->save(); - - $cron = cron::byClassAndFunction('jeedom', 'cron5'); - if (!is_object($cron)) { - echo "Create jeedom::cron5\n"; - $cron = new cron(); - } - $cron->setClass('jeedom'); - $cron->setFunction('cron5'); - $cron->setSchedule('*/5 * * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(5); - $cron->save(); - - $cron = cron::byClassAndFunction('jeedom', 'cron10'); - if (!is_object($cron)) { - echo "Create jeedom::cron10\n"; - $cron = new cron(); - } - $cron->setClass('jeedom'); - $cron->setFunction('cron10'); - $cron->setSchedule('*/10 * * * *'); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(10); - $cron->save(); - - $cron = cron::byClassAndFunction('jeedom', 'cron'); - if (!is_object($cron)) { - echo "Create jeedom::cron\n"; - $cron = new cron(); - } - $cron->setClass('jeedom'); - $cron->setFunction('cron'); - $cron->setSchedule('* * * * *'); - $cron->setTimeout(2); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('plugin', 'cron'); - if (!is_object($cron)) { - echo "Create plugin::cron\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('cron'); - $cron->setSchedule('* * * * *'); - $cron->setTimeout(2); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('queue', 'cron'); - if (!is_object($cron)) { - echo "Create queue::cron\n"; - $cron = new cron(); - } - $cron->setClass('queue'); - $cron->setFunction('cron'); - $cron->setSchedule('* * * * *'); - $cron->setTimeout(2); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('plugin', 'cron5'); - if (!is_object($cron)) { - echo "Create plugin::cron5\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('cron5'); - $cron->setSchedule('*/5 * * * *'); - $cron->setTimeout(5); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('plugin', 'cron10'); - if (!is_object($cron)) { - echo "Create plugin::cron10\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); + $cron = cron::byClassAndFunction('plugin', 'cronDaily'); + if (!is_object($cron)) { + echo "Create plugin::cronDaily\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('cronDaily'); + $cron->setSchedule('00 00 * * *'); + $cron->setTimeout(240); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('jeedom', 'backup'); + if (!is_object($cron)) { + echo "Create jeedom::backup\n"; + $cron = new cron(); + $cron->setSchedule(rand(10, 59) . ' 0' . rand(0, 9) . ' * * *'); + } + if(config::byKey('market::cloudUpload', 0) == 1){ + $cron->setSchedule(rand(10, 59) . ' 0' . rand(0, 9) . ' * * *'); + } + $cron->setClass('jeedom'); + $cron->setFunction('backup'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(60); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'cronHourly'); + if (!is_object($cron)) { + echo "Create plugin::cronHourly\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('cronHourly'); + $cron->setSchedule('00 * * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(60); + $cron->save(); + + $cron = cron::byClassAndFunction('scenario', 'check'); + if (!is_object($cron)) { + echo "Create scenario::check\n"; + $cron = new cron(); + } + $cron->setClass('scenario'); + $cron->setFunction('check'); + $cron->setSchedule('* * * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(30); + $cron->save(); + + $cron = cron::byClassAndFunction('scenario', 'control'); + if (!is_object($cron)) { + echo "Create scenario::control\n"; + $cron = new cron(); + } + $cron->setClass('scenario'); + $cron->setFunction('control'); + $cron->setSchedule('* * * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(30); + $cron->save(); + + $cron = cron::byClassAndFunction('jeedom', 'cronDaily'); + if (!is_object($cron)) { + echo "Create jeedom::cronDaily\n"; + $cron = new cron(); + } + $cron->setClass('jeedom'); + $cron->setFunction('cronDaily'); + $cron->setSchedule(rand(0, 59) . ' ' . rand(0, 3) . ' * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(240); + $cron->save(); + + $cron = cron::byClassAndFunction('jeedom', 'cronHourly'); + if (!is_object($cron)) { + echo "Create jeedom::cronHourly\n"; + $cron = new cron(); + } + $cron->setClass('jeedom'); + $cron->setFunction('cronHourly'); + $cron->setSchedule(rand(0, 59) . ' * * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(60); + $cron->save(); + + $cron = cron::byClassAndFunction('jeedom', 'cron5'); + if (!is_object($cron)) { + echo "Create jeedom::cron5\n"; + $cron = new cron(); + } + $cron->setClass('jeedom'); + $cron->setFunction('cron5'); + $cron->setSchedule('*/5 * * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(5); + $cron->save(); + + $cron = cron::byClassAndFunction('jeedom', 'cron10'); + if (!is_object($cron)) { + echo "Create jeedom::cron10\n"; + $cron = new cron(); + } + $cron->setClass('jeedom'); + $cron->setFunction('cron10'); + $cron->setSchedule('*/10 * * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(10); + $cron->save(); + + $cron = cron::byClassAndFunction('jeedom', 'cron'); + if (!is_object($cron)) { + echo "Create jeedom::cron\n"; + $cron = new cron(); + } + $cron->setClass('jeedom'); + $cron->setFunction('cron'); + $cron->setSchedule('* * * * *'); + $cron->setTimeout(2); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'cron'); + if (!is_object($cron)) { + echo "Create plugin::cron\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('cron'); + $cron->setSchedule('* * * * *'); + $cron->setTimeout(2); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('queue', 'cron'); + if (!is_object($cron)) { + echo "Create queue::cron\n"; + $cron = new cron(); + } + $cron->setClass('queue'); + $cron->setFunction('cron'); + $cron->setSchedule('* * * * *'); + $cron->setTimeout(2); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'cron5'); + if (!is_object($cron)) { + echo "Create plugin::cron5\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('cron5'); + $cron->setSchedule('*/5 * * * *'); + $cron->setTimeout(5); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'cron10'); + if (!is_object($cron)) { + echo "Create plugin::cron10\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('cron10'); + $cron->setSchedule('*/10 * * * *'); + $cron->setTimeout(10); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'cron15'); + if (!is_object($cron)) { + echo "Create plugin::cron15\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('cron15'); + $cron->setSchedule('*/15 * * * *'); + $cron->setTimeout(15); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'cron30'); + if (!is_object($cron)) { + echo "Create plugin::cron30\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('cron30'); + $cron->setSchedule('*/30 * * * *'); + $cron->setTimeout(30); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'checkDeamon'); + if (!is_object($cron)) { + echo "Create plugin::checkDeamon\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('checkDeamon'); + $cron->setSchedule('*/5 * * * *'); + $cron->setTimeout(5); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('cache', 'persist'); + if (!is_object($cron)) { + echo "Create cache::persist\n"; + $cron = new cron(); + } + $cron->setClass('cache'); + $cron->setFunction('persist'); + $cron->setSchedule('*/30 * * * *'); + $cron->setTimeout(30); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('history', 'archive'); + if (!is_object($cron)) { + echo "Create history::archive\n"; + $cron = new cron(); + } + $cron->setClass('history'); + $cron->setFunction('archive'); + $cron->setSchedule('00 5 * * *'); + $cron->setTimeout(240); + $cron->setDeamon(0); + $cron->save(); + + $cron = cron::byClassAndFunction('plugin', 'heartbeat'); + if (!is_object($cron)) { + echo "Create plugin::heartbeat\n"; + $cron = new cron(); + } + $cron->setClass('plugin'); + $cron->setFunction('heartbeat'); + $cron->setSchedule('*/5 * * * *'); + $cron->setEnable(1); + $cron->setDeamon(0); + $cron->setTimeout(10); + $cron->save(); + + $cron = cron::byClassAndFunction('network', 'cron10'); + if (!is_object($cron)) { + echo "Create network::cron10\n"; + $cron = new cron(); + $cron->setClass('network'); $cron->setFunction('cron10'); - $cron->setSchedule('*/10 * * * *'); - $cron->setTimeout(10); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('plugin', 'cron15'); - if (!is_object($cron)) { - echo "Create plugin::cron15\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('cron15'); - $cron->setSchedule('*/15 * * * *'); - $cron->setTimeout(15); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('plugin', 'cron30'); - if (!is_object($cron)) { - echo "Create plugin::cron30\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('cron30'); - $cron->setSchedule('*/30 * * * *'); - $cron->setTimeout(30); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('plugin', 'checkDeamon'); - if (!is_object($cron)) { - echo "Create plugin::checkDeamon\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('checkDeamon'); - $cron->setSchedule('*/5 * * * *'); - $cron->setTimeout(5); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('cache', 'persist'); - if (!is_object($cron)) { - echo "Create cache::persist\n"; - $cron = new cron(); - } - $cron->setClass('cache'); - $cron->setFunction('persist'); - $cron->setSchedule('*/30 * * * *'); - $cron->setTimeout(30); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('history', 'archive'); - if (!is_object($cron)) { - echo "Create history::archive\n"; - $cron = new cron(); + $rand_min = rand(0,9); + $cronString = ''; + for($i=0;$i<6;$i++){ + $cronString .= ($rand_min+($i*10)).','; } - $cron->setClass('history'); - $cron->setFunction('archive'); - $cron->setSchedule('00 5 * * *'); - $cron->setTimeout(240); - $cron->setDeamon(0); - $cron->save(); - - $cron = cron::byClassAndFunction('plugin', 'heartbeat'); - if (!is_object($cron)) { - echo "Create plugin::heartbeat\n"; - $cron = new cron(); - } - $cron->setClass('plugin'); - $cron->setFunction('heartbeat'); - $cron->setSchedule('*/5 * * * *'); + $cronString = trim($cronString,',').' * * * *'; + $cron->setSchedule($cronString); $cron->setEnable(1); $cron->setDeamon(0); - $cron->setTimeout(10); + $cron->setTimeout(60); $cron->save(); + } - $cron = cron::byClassAndFunction('network', 'cron10'); - if (!is_object($cron)) { - echo "Create network::cron10\n"; - $cron = new cron(); - $cron->setClass('network'); - $cron->setFunction('cron10'); - $rand_min = rand(0,9); - $cronString = ''; - for($i=0;$i<6;$i++){ - $cronString .= ($rand_min+($i*10)).','; - } - $cronString = trim($cronString,',').' * * * *'; - $cron->setSchedule($cronString); - $cron->setEnable(1); - $cron->setDeamon(0); - $cron->setTimeout(60); - $cron->save(); - } + if (!file_exists(__DIR__ . '/../plugins')) { + mkdir(__DIR__ . '/../plugins'); + } + try { + echo "\nCheck filesystem right..."; + jeedom::cleanFileSystemRight(); + echo "OK\n"; + } catch (Exception $e) { + echo "NOK\n"; + } - if (!file_exists(__DIR__ . '/../plugins')) { - mkdir(__DIR__ . '/../plugins'); - } + config::save('hardware_name', ''); + if (config::byKey('api') == '') { + echo "Fix default apikey...\n"; + config::save('api', config::genKey()); + } + if (file_exists(__DIR__ . '/../core/nodeJS')) { + echo "Remove unused nodejs folder...\n"; + shell_exec(system::getCmdSudo() . 'rm -rf ' . __DIR__ . '/../core/nodeJS'); + } + if (file_exists(__DIR__ . '/../script/ngrok')) { + echo "Remove unused ngrok folder...\n"; + shell_exec(system::getCmdSudo() . 'rm -rf ' . __DIR__ . '/../script/ngrok'); + } + + echo "Check jeedom object..."; + foreach (jeeObject::all() as $object) { try { - echo "\nCheck filesystem right..."; - jeedom::cleanFileSystemRight(); - echo "OK\n"; - } catch (Exception $e) { - echo "NOK\n"; + $object->save(); + } catch (Exception $exc) { } + } + echo "OK\n"; - config::save('hardware_name', ''); - if (config::byKey('api') == '') { - echo "Fix default apikey...\n"; - config::save('api', config::genKey()); - } - if (file_exists(__DIR__ . '/../core/nodeJS')) { - echo "Remove unused nodejs folder...\n"; - shell_exec(system::getCmdSudo() . 'rm -rf ' . __DIR__ . '/../core/nodeJS'); - } - if (file_exists(__DIR__ . '/../script/ngrok')) { - echo "Remove unused ngrok folder...\n"; - shell_exec(system::getCmdSudo() . 'rm -rf ' . __DIR__ . '/../script/ngrok'); - } - - echo "Check jeedom object..."; - foreach (jeeObject::all() as $object) { - try { - $object->save(); - } catch (Exception $exc) { + echo "Check jeedom cmd..."; + foreach (cmd::all() as $cmd) { + try { + $changed = false; + if ($cmd->getConfiguration('jeedomCheckCmdCmdActionId') != '') { + $cmd->setConfiguration('jeedomCheckCmdCmdActionId', ''); + $changed = true; } - } - echo "OK\n"; - - echo "Check jeedom cmd..."; - foreach (cmd::all() as $cmd) { - try { - $changed = false; - if ($cmd->getConfiguration('jeedomCheckCmdCmdActionId') != '') { - $cmd->setConfiguration('jeedomCheckCmdCmdActionId', ''); - $changed = true; - } - if (trim($cmd->getTemplate('dashboard')) != '' && strpos($cmd->getTemplate('dashboard'), '::') === false) { - $cmd->setTemplate('dashboard', 'core::' . $cmd->getTemplate('dashboard')); - $changed = true; - } - if (trim($cmd->getTemplate('mobile')) != '' && strpos($cmd->getTemplate('mobile'), '::') === false) { - $cmd->setTemplate('mobile', 'core::' . $cmd->getTemplate('mobile')); - $changed = true; - } - if ($changed) { - $cmd->save(true); - } - } catch (Exception $exc) { + if (trim($cmd->getTemplate('dashboard')) != '' && strpos($cmd->getTemplate('dashboard'), '::') === false) { + $cmd->setTemplate('dashboard', 'core::' . $cmd->getTemplate('dashboard')); + $changed = true; + } + if (trim($cmd->getTemplate('mobile')) != '' && strpos($cmd->getTemplate('mobile'), '::') === false) { + $cmd->setTemplate('mobile', 'core::' . $cmd->getTemplate('mobile')); + $changed = true; + } + if ($changed) { + $cmd->save(true); } + } catch (Exception $exc) { } } + echo "OK\n"; if (!file_exists(__DIR__ . '/../data/php/user.function.class.php')) { diff --git a/install/install.sh b/install/install.sh index bb55dafea1..f6db7fa612 100644 --- a/install/install.sh +++ b/install/install.sh @@ -331,9 +331,6 @@ step_10_jeedom_installation() { export COMPOSER_ALLOW_SUPERUSER=1 cd ${WEBSERVER_HOME} composer install --no-ansi --no-dev --no-interaction --no-plugins --no-progress --no-scripts --optimize-autoloader - mkdir -p /tmp/jeedom - chmod 777 -R /tmp/jeedom - chown www-data:www-data -R /tmp/jeedom if [ "${INSTALLATION_TYPE}" != "docker" ];then php ${WEBSERVER_HOME}/install/install.php mode=force @@ -349,6 +346,9 @@ step_10_jeedom_installation() { step_11_jeedom_post() { echo "---------------------------------------------------------------------" echo "${YELLOW}Starting step 11 - Jeedom post-install${NORMAL}" + mkdir -p /tmp/jeedom + chmod 777 -R /tmp/jeedom + chown www-data:www-data -R /tmp/jeedom if [ $(crontab -l | grep jeedom | wc -l) -ne 0 ];then (echo crontab -l | grep -v "jeedom") | crontab - diff --git a/install/update.php b/install/update.php index 76d0fc3dea..c1486b89ea 100644 --- a/install/update.php +++ b/install/update.php @@ -30,7 +30,7 @@ $update_begin = false; try { require_once __DIR__ . '/../core/php/core.inc.php'; - + echo "[PROGRESS][1]\n"; if (count(system::ps('install/update.php', 'sudo')) > 1) { echo "Update in progress. I will wait 10s\n"; @@ -76,7 +76,7 @@ echo "[PROGRESS][5]\n"; try { echo "Check rights..."; - if(method_exists('jeedom','cleanFileSystemRight')){ + if (method_exists('jeedom', 'cleanFileSystemRight')) { jeedom::cleanFileSystemRight(); } echo "OK\n"; @@ -111,9 +111,9 @@ $tmp = $tmp_dir . '/jeedom_update.zip'; try { if (config::byKey('core::repo::provider') == 'default') { - if(strpos(config::byKey('core::branch'),'tag::') === 0){ - $url = 'https://github.com/jeedom/core/archive/refs/tags/'.str_replace('tag::','',config::byKey('core::branch')).'.zip'; - }else{ + if (strpos(config::byKey('core::branch'), 'tag::') === 0) { + $url = 'https://github.com/jeedom/core/archive/refs/tags/' . str_replace('tag::', '', config::byKey('core::branch')) . '.zip'; + } else { $url = 'https://github.com/jeedom/core/archive/' . config::byKey('core::branch') . '.zip'; } echo "Download url : " . $url . "\n"; @@ -161,14 +161,14 @@ echo "[PROGRESS][35]\n"; echo "Unzip in progress..."; $zip = new ZipArchive; - $open = $zip->open($tmp); + $open = $zip->open($tmp); if ($open === TRUE) { if (!$zip->extractTo($cibDir)) { - throw new Exception('Can not unzip file => '.$zip->getStatusString()); + throw new Exception('Can not unzip file => ' . $zip->getStatusString()); } $zip->close(); } else { - throw new Exception('Unable to unzip file : ' . $tmp.' =>'.$open); + throw new Exception('Unable to unzip file : ' . $tmp . ' =>' . $open); } echo "OK\n"; if (disk_free_space($cibDir) < 10) { @@ -181,7 +181,7 @@ $cibDir = $cibDir . '/' . $files[0]; } } - + if (init('preUpdate') == 1) { echo "Update updater..."; rmove($cibDir . '/install/update.php', __DIR__ . '/update.php', false, array(), array('log' => true, 'ignoreFileSizeUnder' => 1)); @@ -215,9 +215,9 @@ shell_exec('rm -rf ' . $cibDir . '/vendor'); echo "OK\n"; echo "[PROGRESS][46]\n"; - + echo "Update modification date of unzip file..."; - shell_exec('find '.$cibDir.'/ -exec touch {} +'); + shell_exec('find ' . $cibDir . '/ -exec touch {} +'); echo "OK\n"; echo "[PROGRESS][47]\n"; @@ -237,7 +237,7 @@ } echo "OK\n"; echo "[PROGRESS][52]\n"; - if(strpos(config::byKey('core::branch'),'tag::') !== 0){ + if (strpos(config::byKey('core::branch'), 'tag::') !== 0) { echo "Remove useless files...\n"; foreach (array('3rdparty', 'desktop', 'mobile', 'core', 'docs', 'install', 'script') as $folder) { echo 'Cleaning ' . $folder . "\n"; @@ -248,16 +248,16 @@ echo "[PROGRESS][53]\n"; if (exec('which composer | wc -l') == 0) { echo "\nNeed to install composer..."; - echo shell_exec(system::getCmdSudo().' ' . __DIR__ . '/../resources/install_composer.sh'); + echo shell_exec(system::getCmdSudo() . ' ' . __DIR__ . '/../resources/install_composer.sh'); echo "OK\n"; } echo "Update composer file...\n"; if (exec('which composer | wc -l') > 0) { - shell_exec(system::getCmdSudo(). ' rm '. __DIR__ . '/../composer.lock'); - shell_exec('export COMPOSER_HOME="/tmp/composer";export COMPOSER_ALLOW_SUPERUSER=1;'.system::getCmdSudo().' composer self-update > /dev/null 2>&1'); - shell_exec('cd ' . __DIR__ . '/../;export COMPOSER_ALLOW_SUPERUSER=1;export COMPOSER_HOME="/tmp/composer";'.system::getCmdSudo().' composer update --no-interaction --no-plugins --no-scripts --no-ansi --no-dev --no-progress --optimize-autoloader --with-all-dependencies --no-cache > /dev/null 2>&1'); - shell_exec(system::getCmdSudo().' rm /tmp/composer 2>/dev/null'); - if(method_exists('jeedom','cleanFileSystemRight')){ + shell_exec(system::getCmdSudo() . ' rm ' . __DIR__ . '/../composer.lock'); + shell_exec('export COMPOSER_HOME="/tmp/composer";export COMPOSER_ALLOW_SUPERUSER=1;' . system::getCmdSudo() . ' composer self-update > /dev/null 2>&1'); + shell_exec('cd ' . __DIR__ . '/../;export COMPOSER_ALLOW_SUPERUSER=1;export COMPOSER_HOME="/tmp/composer";' . system::getCmdSudo() . ' composer update --no-interaction --no-plugins --no-scripts --no-ansi --no-dev --no-progress --optimize-autoloader --with-all-dependencies --no-cache > /dev/null 2>&1'); + shell_exec(system::getCmdSudo() . ' rm /tmp/composer 2>/dev/null'); + if (method_exists('jeedom', 'cleanFileSystemRight')) { jeedom::cleanFileSystemRight(); } } @@ -396,6 +396,9 @@ function incrementVersion($_version) { $version = explode('.', $_version); + if (!isset($version[2])) { + $version[2] = 0; + } if ($version[2] < 100) { $version[2]++; } else { diff --git a/install/update/4.5.3.php b/install/update/4.5.3.php new file mode 100644 index 0000000000..efeb5ddc77 --- /dev/null +++ b/install/update/4.5.3.php @@ -0,0 +1,25 @@ + 'Odroid (old)', + 'Odroid ARMBIAN (Buster)' => 'Odroid', + 'Banana PI' => 'Banana Pi', + 'Orange PI' => 'Orange Pi', + 'Raspberry pi' => 'Raspberry Pi' +); + +$hardware = strtolower(jeedom::getHardwareName()); +if ($hardware === 'smart') { + $renamed['Odroid ARMBIAN (Buster)'] = 'Jeedom Smart'; +} +if ($hardware !== 'atlas') { + $renamed['Jeedom Atlas'] = 'Rock Pi'; +} + +foreach ($renamed as $previousValue => $newValue) { + foreach ((config::byValue($previousValue, 'port')) as $config) { + config::save('port', $newValue, $config['plugin']); + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fec11fd743..678a40ab7c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,95 +1,35 @@ parameters: ignoreErrors: - - - message: '#^Static method cache\:\:set\(\) invoked with 4 parameters, 2\-3 required\.$#' - identifier: arguments.count - count: 1 - path: core/ajax/cache.ajax.php - - - - message: '#^Static call to instance method cmd\:\:dropInfluxDatabase\(\)\.$#' - identifier: method.staticCall - count: 1 - path: core/ajax/cmd.ajax.php - - message: '#^Static call to instance method cmd\:\:historyInfluxAll\(\)\.$#' identifier: method.staticCall count: 1 path: core/ajax/cmd.ajax.php - - - message: '#^Static method history\:\:getHistoryFromCalcul\(\) invoked with 6 parameters, 1\-5 required\.$#' - identifier: arguments.count - count: 1 - path: core/ajax/cmd.ajax.php - - message: '#^Variable \$eqLogic might not be defined\.$#' identifier: variable.undefined count: 1 path: core/ajax/eqLogic.ajax.php - - - message: '#^Static method interactQuery\:\:byInteractDefId\(\) invoked with 2 parameters, 1 required\.$#' - identifier: arguments.count - count: 2 - path: core/ajax/interact.ajax.php - - - - message: '#^Static method listener\:\:all\(\) invoked with 1 parameter, 0 required\.$#' - identifier: arguments.count - count: 1 - path: core/ajax/listener.ajax.php - - - - message: '#^Static method jeeObject\:\:getUISelectList\(\) invoked with 2 parameters, 0\-1 required\.$#' - identifier: arguments.count - count: 1 - path: core/ajax/object.ajax.php - - - - message: '#^Static method queue\:\:all\(\) invoked with 1 parameter, 0 required\.$#' - identifier: arguments.count - count: 1 - path: core/ajax/queue.ajax.php - - message: '#^Variable \$market might not be defined\.$#' identifier: variable.undefined count: 1 path: core/ajax/repo.ajax.php - - - message: '#^Variable \$return might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: core/ajax/report.ajax.php - - message: '#^Variable \$converts might not be defined\.$#' identifier: variable.undefined count: 2 path: core/ajax/scenario.ajax.php - - - message: '#^Result of static method user\:\:removeBanIp\(\) \(void\) is used\.$#' - identifier: staticMethod.void - count: 1 - path: core/ajax/user.ajax.php - - message: '#^Variable \$configs might not be defined\.$#' identifier: variable.undefined count: 1 path: core/ajax/user.ajax.php - - - message: '#^Static method jeedom\:\:update\(\) invoked with 2 parameters, 0\-1 required\.$#' - identifier: arguments.count - count: 2 - path: core/api/jeeApi.php - - message: '#^Variable \$cmd might not be defined\.$#' identifier: variable.undefined @@ -126,42 +66,6 @@ parameters: count: 2 path: core/api/jeeApi.php - - - message: '#^Call to static method byId\(\) on an unknown class jeeNetwork\.$#' - identifier: class.notFound - count: 1 - path: core/api/proApi.php - - - - message: '#^Call to static method byId\(\) on an unknown class market\.$#' - identifier: class.notFound - count: 2 - path: core/api/proApi.php - - - - message: '#^Call to static method byLogicalId\(\) on an unknown class market\.$#' - identifier: class.notFound - count: 1 - path: core/api/proApi.php - - - - message: '#^Call to static method testMaster\(\) on an unknown class jeeNetwork\.$#' - identifier: class.notFound - count: 1 - path: core/api/proApi.php - - - - message: '#^Static method jeedom\:\:update\(\) invoked with 2 parameters, 0\-1 required\.$#' - identifier: arguments.count - count: 2 - path: core/api/proApi.php - - - - message: '#^Static method repo_market\:\:backup_restore\(\) invoked with 2 parameters, 1 required\.$#' - identifier: arguments.count - count: 1 - path: core/api/proApi.php - - message: '#^Instantiated class InfluxDB\\Client not found\.$#' identifier: class.notFound @@ -180,12 +84,6 @@ parameters: count: 3 path: core/class/history.class.php - - - message: '#^Static call to instance method interactQuery\:\:replaceForContextual\(\)\.$#' - identifier: method.staticCall - count: 3 - path: core/class/interactQuery.class.php - - message: '#^Variable \$closest might not be defined\.$#' identifier: variable.undefined @@ -258,18 +156,6 @@ parameters: count: 1 path: core/class/scenario.class.php - - - message: '#^Instantiated class SolarData\\SolarData not found\.$#' - identifier: class.notFound - count: 1 - path: core/class/scenarioExpression.class.php - - - - message: '#^Static method update\:\:getLastAvailableVersion\(\) invoked with 1 parameter, 0 required\.$#' - identifier: arguments.count - count: 1 - path: core/class/update.class.php - - message: '#^Variable \$url might not be defined\.$#' identifier: variable.undefined diff --git a/resources/install_nodejs.sh b/resources/install_nodejs.sh index 695498469e..c83ccc1d9f 100644 --- a/resources/install_nodejs.sh +++ b/resources/install_nodejs.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -installVer='20' #NodeJS major version to be installed -minVer='20' #min NodeJS major version to be accepted +installVer='22' #NodeJS major version to be installed +minVer='22' #min NodeJS major version to be accepted # vérifier si toujours nécessaire, cette source traine encore sur certaines smart et si une source est invalide -> nodejs ne s'installera pas if ls /etc/apt/sources.list.d/deb-multimedia.list* &>/dev/null; then @@ -53,8 +53,8 @@ lsb_release -c | grep jessie if [ $? -eq 0 ] then today=$(date +%Y%m%d) - if [[ "$today" > "20200630" ]]; - then + if [[ "$today" > "20200630" ]]; + then echo "== ATTENTION Debian 8 Jessie n'est officiellement plus supportée depuis le 30 juin 2020, merci de mettre à jour votre distribution !!!" exit 1 fi @@ -65,8 +65,8 @@ lsb_release -c | grep stretch if [ $? -eq 0 ] then today=$(date +%Y%m%d) - if [[ "$today" > "20220630" ]]; - then + if [[ "$today" > "20220630" ]]; + then echo "== ATTENTION Debian 9 Stretch n'est officiellement plus supportée depuis le 30 juin 2022, merci de mettre à jour votre distribution !!!" exit 1 fi @@ -77,8 +77,8 @@ lsb_release -c | grep buster if [ $? -eq 0 ] then today=$(date +%Y%m%d) - if [[ "$today" > "20220630" ]]; - then + if [[ "$today" > "20220630" ]]; + then echo "== ATTENTION Debian 10 Buster n'est officiellement plus supportée depuis le 30 juin 2024, merci de mettre à jour votre distribution !!!" exit 1 fi @@ -87,9 +87,9 @@ fi #x86 32 bits not supported by nodesource anymore bits=$(getconf LONG_BIT) if { [ "$arch" = "i386" ] || [ "$arch" = "i686" ]; } && [ "$bits" -eq "32" ] -then +then echo "== ATTENTION Votre système est x86 en 32bits et NodeJS 12 n'y est pas supporté, merci de passer en 64bits !!!" -exit 1 +exit 1 fi @@ -104,7 +104,7 @@ then else echo "[ KO ]"; echo "Installation de NodeJS $installVer" - + #if npm exists type npm &>/dev/null if [ $? -eq 0 ]; then @@ -112,10 +112,10 @@ else else npmPrefix="/usr" fi - + sudo DEBIAN_FRONTEND=noninteractive apt-get -y --purge autoremove npm &>/dev/null sudo DEBIAN_FRONTEND=noninteractive apt-get -y --purge autoremove nodejs &>/dev/null - + if [[ $arch == "armv6l" ]] then #version to install for armv6 (to check on https://unofficial-builds.nodejs.org) @@ -152,13 +152,13 @@ else sudo apt-get update sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs fi - + npm config set prefix ${npmPrefix} &>/dev/null - if [ $(which node | wc -l) -ne 0 ] && [ $(which nodejs | wc -l) -eq 0 ]; then + if [ $(which node | wc -l) -ne 0 ] && [ $(which nodejs | wc -l) -eq 0 ]; then ln -s $(which node) $(which node)js fi - + new=`nodejs -v`; echo -n "[Check Version NodeJS après install : ${new} : " testVerAfter=$(php -r "echo version_compare('${new}','v${minVer}','>=');") @@ -173,7 +173,7 @@ fi type npm &>/dev/null if [ $? -ne 0 ]; then # Installation de npm car non présent (par sécu) - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y npm + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y npm sudo npm install -g npm fi @@ -183,7 +183,7 @@ if [ $? -eq 0 ]; then npmPrefixSudo=`sudo npm prefix -g` npmPrefixwwwData=`sudo -u www-data npm prefix -g` echo -n "[Check Prefix : $npmPrefix and sudo prefix : $npmPrefixSudo and www-data prefix : $npmPrefixwwwData : " - if [[ "$npmPrefixSudo" != "/usr" ]] && [[ "$npmPrefixSudo" != "/usr/local" ]]; then + if [[ "$npmPrefixSudo" != "/usr" ]] && [[ "$npmPrefixSudo" != "/usr/local" ]]; then echo "[ KO ]" if [[ "$npmPrefixwwwData" == "/usr" ]] || [[ "$npmPrefixwwwData" == "/usr/local" ]]; then echo "Reset prefix ($npmPrefixwwwData) pour npm `sudo whoami`" @@ -202,7 +202,7 @@ if [ $? -eq 0 ]; then sudo npm config set prefix /usr/local fi fi - fi + fi else if [[ "$npmPrefixwwwData" == "/usr" ]] || [[ "$npmPrefixwwwData" == "/usr/local" ]]; then if [[ "$npmPrefixwwwData" == "$npmPrefixSudo" ]]; then diff --git a/tests/integration/support/AjaxIntegrationTestCase.php b/tests/integration/support/AjaxIntegrationTestCase.php new file mode 100644 index 0000000000..5e8d75a32e --- /dev/null +++ b/tests/integration/support/AjaxIntegrationTestCase.php @@ -0,0 +1,180 @@ +. +*/ + +use PHPUnit\Framework\TestCase; + +abstract class AjaxIntegrationTestCase extends TestCase { + + /** @var callable[] */ + private $cleanupStack = array(); + + /** @var int|null */ + private $authenticatedUserId = null; + + protected function tearDown(): void { + foreach (array_reverse($this->cleanupStack) as $callback) { + try { + $callback(); + } catch (\Throwable $e) { + fwrite(STDERR, "Cleanup failed: " . $e->getMessage() . "\n"); + } + } + $this->cleanupStack = []; + $this->authenticatedUserId = null; + } + + protected function givenAdminIsLoggedIn(): void { + $admin = new user(); + $admin->setLogin('test_admin_' . bin2hex(random_bytes(4))); + $admin->setPassword(sha512('test_password_' . bin2hex(random_bytes(4)))); + $admin->setProfils('admin'); + $admin->setEnable(1); + $admin->save(); + + $this->registerCleanup(function () use ($admin) { + $admin->remove(); + }); + $this->authenticatedUserId = (int)$admin->getId(); + } + + /** + * Creates a view + zone + viewData ready to be targeted by setComponentOrder. + * The link_id intentionally points to a non-existent id so that the + * eqLogic::byId() branch of the handler is short-circuited: we only test + * the order UPDATE query, not the side-effects. + */ + protected function givenAViewDataWith(string $type, int $initialOrder = 5): object { + $view = new view(); + $view->setName('test_view_' . bin2hex(random_bytes(4))); + $view->save(); + $this->registerCleanup(function () use ($view) { + $view->remove(); + }); + + $viewZone = new viewZone(); + $viewZone->setView_id($view->getId()); + $viewZone->setName('test_zone'); + $viewZone->save(); + + $fakeLinkId = 987654321; + $viewData = new viewData(); + $viewData->setviewZone_id($viewZone->getId()); + $viewData->setType($type); + $viewData->setLink_id($fakeLinkId); + $viewData->setOrder($initialOrder); + $viewData->save(); + + return (object)[ + 'viewId' => (int)$view->getId(), + 'viewZoneId' => (int)$viewZone->getId(), + 'viewDataId' => (int)$viewData->getId(), + 'linkId' => $fakeLinkId, + 'initialOrder' => $initialOrder, + ]; + } + + protected function whenSetComponentOrderIsCalledWith(array $components): array { + return $this->callAjaxEndpoint('view.ajax.php', [ + 'action' => 'setComponentOrder', + 'components' => json_encode($components), + ]); + } + + protected function thenResponseIsJsonSuccess(array $response): void { + $this->assertSame( + 0, + $response['exitCode'], + "Subprocess exited non-zero.\nStdout: {$response['body']}\nStderr: {$response['stderr']}" + ); + $this->assertNotNull( + $response['json'], + "Expected JSON response.\nStdout: {$response['body']}\nStderr: {$response['stderr']}" + ); + $this->assertSame( + 'ok', + $response['json']['state'] ?? null, + "Expected state=ok.\nBody: {$response['body']}" + ); + } + + protected function thenViewDataOrderIs(int $viewDataId, int $expectedOrder): void { + $row = DB::Prepare( + 'SELECT `order` FROM viewData WHERE id = :id', + ['id' => $viewDataId], + DB::FETCH_TYPE_ROW + ); + $this->assertIsArray($row, "viewData id={$viewDataId} not found"); + $this->assertSame( + $expectedOrder, + (int)$row['order'], + "viewData.order was modified unexpectedly" + ); + } + + protected function thenTableStillExists(string $table): void { + $row = DB::Prepare( + 'SHOW TABLES LIKE :table', + ['table' => $table], + DB::FETCH_TYPE_ROW + ); + $this->assertNotEmpty($row, "Table '{$table}' no longer exists"); + } + + private function registerCleanup(callable $callback): void { + $this->cleanupStack[] = $callback; + } + + private function callAjaxEndpoint(string $ajaxFile, array $postData): array { + $this->assertNotNull( + $this->authenticatedUserId, + 'Call givenAdminIsLoggedIn() before calling any when...()' + ); + + $payload = json_encode([ + 'file' => $ajaxFile, + 'post' => $postData, + 'user_id' => $this->authenticatedUserId, + ]); + + $runner = __DIR__ . '/ajax_runner.php'; + $descriptors = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $process = proc_open('php ' . escapeshellarg($runner), $descriptors, $pipes); + if (!is_resource($process)) { + $this->fail('Failed to spawn ajax_runner.php subprocess'); + } + + fwrite($pipes[0], $payload); + fclose($pipes[0]); + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); + $exitCode = proc_close($process); + + return [ + 'body' => $stdout, + 'stderr' => $stderr, + 'exitCode' => $exitCode, + 'json' => json_decode($stdout, true), + ]; + } +} diff --git a/tests/integration/support/ajax_runner.php b/tests/integration/support/ajax_runner.php new file mode 100644 index 0000000000..bfd3286565 --- /dev/null +++ b/tests/integration/support/ajax_runner.php @@ -0,0 +1,56 @@ +. +*/ + +$payload = json_decode(file_get_contents('php://stdin'), true); +if (!is_array($payload)) { + fwrite(STDERR, "Invalid JSON payload on stdin\n"); + exit(1); +} + +require_once __DIR__ . '/../../../core/php/core.inc.php'; + +$_SERVER['REQUEST_METHOD'] = 'POST'; +$_SERVER['HTTP_HOST'] = 'localhost'; +$_SERVER['SERVER_NAME'] = 'localhost'; +$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; +$_POST = $payload['post'] ?? []; +$_GET = $payload['get'] ?? []; +$_REQUEST = array_merge($_GET, $_POST); + +if (isset($payload['user_id'])) { + $user = user::byId($payload['user_id']); + if (!is_object($user)) { + fwrite(STDERR, "User {$payload['user_id']} not found\n"); + exit(1); + } + $sessionId = bin2hex(random_bytes(32)); + session_id($sessionId); + @session_start(); + $_SESSION['user'] = $user; + $_SESSION['ip'] = '127.0.0.1'; + @session_write_close(); + $_COOKIE[session_name()] = $sessionId; +} + +$ajaxFile = __DIR__ . '/../../../core/ajax/' . basename($payload['file']); +if (!is_file($ajaxFile)) { + fwrite(STDERR, "Ajax file not found: {$ajaxFile}\n"); + exit(1); +} + +include $ajaxFile; diff --git a/tests/integration/viewAjaxSqlInjectionTest.php b/tests/integration/viewAjaxSqlInjectionTest.php new file mode 100644 index 0000000000..a740e68ee3 --- /dev/null +++ b/tests/integration/viewAjaxSqlInjectionTest.php @@ -0,0 +1,100 @@ +. +*/ + +require_once __DIR__ . '/support/AjaxIntegrationTestCase.php'; + +class viewAjaxSqlInjectionTest extends AjaxIntegrationTestCase { + + public function testValidComponentTypeUpdatesTheOrder(): void { + $this->givenAdminIsLoggedIn(); + $component = $this->givenAViewDataWith('eqLogic', 5); + + $response = $this->whenSetComponentOrderIsCalledWith([[ + 'viewZone_id' => $component->viewZoneId, + 'id' => $component->linkId, + 'viewOrder' => 42, + 'type' => 'eqLogic', + ]]); + + $this->thenResponseIsJsonSuccess($response); + $this->thenViewDataOrderIs($component->viewDataId, 42); + } + + public function testSqlInjectionPayloadInTypeFieldIsRejected(): void { + $this->givenAdminIsLoggedIn(); + $component = $this->givenAViewDataWith('eqLogic', 5); + + $response = $this->whenSetComponentOrderIsCalledWith([[ + 'viewZone_id' => $component->viewZoneId, + 'id' => $component->linkId, + 'viewOrder' => 999, + 'type' => 'eqLogic"; DROP TABLE viewData; --', + ]]); + + $this->thenResponseIsJsonSuccess($response); + $this->thenTableStillExists('viewData'); + $this->thenViewDataOrderIs($component->viewDataId, $component->initialOrder); + } + + public function testUnionBasedSqlInjectionIsRejected(): void { + $this->givenAdminIsLoggedIn(); + $component = $this->givenAViewDataWith('eqLogic', 5); + + $response = $this->whenSetComponentOrderIsCalledWith([[ + 'viewZone_id' => $component->viewZoneId, + 'id' => $component->linkId, + 'viewOrder' => 999, + 'type' => 'eqLogic" UNION SELECT password FROM user --', + ]]); + + $this->thenResponseIsJsonSuccess($response); + $this->thenViewDataOrderIs($component->viewDataId, $component->initialOrder); + } + + public function testUnknownComponentTypeIsRejected(): void { + $this->givenAdminIsLoggedIn(); + // Fixture type matches the injected type so the vulnerable WHERE would hit this row. + $component = $this->givenAViewDataWith('arbitrary_string', 5); + + $response = $this->whenSetComponentOrderIsCalledWith([[ + 'viewZone_id' => $component->viewZoneId, + 'id' => $component->linkId, + 'viewOrder' => 999, + 'type' => 'arbitrary_string', + ]]); + + $this->thenResponseIsJsonSuccess($response); + $this->thenViewDataOrderIs($component->viewDataId, $component->initialOrder); + } + + public function testMissingComponentTypeIsRejected(): void { + $this->givenAdminIsLoggedIn(); + // Fixture type is empty so the vulnerable code — which concatenates a + // null into type="" — would match this row and bump its order. + $component = $this->givenAViewDataWith('', 5); + + $response = $this->whenSetComponentOrderIsCalledWith([[ + 'viewZone_id' => $component->viewZoneId, + 'id' => $component->linkId, + 'viewOrder' => 999, + ]]); + + $this->thenResponseIsJsonSuccess($response); + $this->thenViewDataOrderIs($component->viewDataId, $component->initialOrder); + } +}