diff --git a/.all-contributorsrc b/.all-contributorsrc index bb95740fd01..511f6cd7403 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -705,6 +705,15 @@ "contributions": [ "code" ] + }, + { + "login": "Damzoneuh", + "name": "Damzoneuh", + "avatar_url": "https://avatars.githubusercontent.com/u/44919863?v=4", + "profile": "https://github.com/Damzoneuh", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.ddev/docker-compose.redis.yaml b/.ddev/docker-compose.redis.yaml index 74d8d740ae6..2f98473d85f 100644 --- a/.ddev/docker-compose.redis.yaml +++ b/.ddev/docker-compose.redis.yaml @@ -5,7 +5,7 @@ version: "3.6" services: redis: container_name: ddev-${DDEV_SITENAME}-redis - image: redis:4 + image: redis:5 ports: - 6379 labels: diff --git a/.github/ci-files/.my.cnf b/.github/ci-files/.my.cnf new file mode 100644 index 00000000000..05c4c279513 --- /dev/null +++ b/.github/ci-files/.my.cnf @@ -0,0 +1,5 @@ +[mysqldump] +# the shivammathur/setup-php@v2 github action image uses mysqldump 8 in it's image, +# which enables column statistics by default. +# we don't need / want that, so we disable it here. +column-statistics=0 diff --git a/.github/release-notes.yml b/.github/release-notes.yml deleted file mode 100644 index 75d57bf13d9..00000000000 --- a/.github/release-notes.yml +++ /dev/null @@ -1,7 +0,0 @@ -changelog: - repository: mautic/mautic - sections: - - title: "Enhancements" - labels: ["enhancement", "feature"] - - title: "Bugs" - labels: ["bug", "regression"] diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000000..51cc03e7e30 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,21 @@ +changelog: + exclude: + labels: + - chore + authors: + - github-actions + categories: + - title: 🛠 Breaking Changes + labels: + - bc-break + - title: 🔒 Security + labels: + - security + - title: ✨ Features and enhancements + labels: + - feature + - enhancement + - title: 🐛 Bugs + labels: + - bug + - regression diff --git a/.github/workflows/build-grapesjs-assets.yml b/.github/workflows/build-grapesjs-assets.yml index 9a7d4870bd2..650e0d07cd2 100644 --- a/.github/workflows/build-grapesjs-assets.yml +++ b/.github/workflows/build-grapesjs-assets.yml @@ -14,6 +14,7 @@ defaults: jobs: build-js: runs-on: ubuntu-latest + if: github.repository == 'mautic/mautic' steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7325ab5745d..9db4310232e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,8 @@ jobs: release: name: Create draft release runs-on: ubuntu-latest + if: github.repository == 'mautic/mautic' + steps: - uses: actions/checkout@v2 # Our build script needs access to all previous tags, so we add fetch-depth: 0 @@ -58,33 +60,6 @@ jobs: echo "IS_PRERELEASE=${PRERELEASE}" >> $GITHUB_ENV - - name: "Try generating the release notes" - run: | - # Version as indicated in the metadata, e.g., 3.2.5-rc - METADATA_VERSION=$(jq -r '.version' app/release_metadata.json) - - # We only need the first part of the version, e.g. 3.2.5, for the milestone recognition. Split on the dash (-) character. - METADATA_ARRAY=(${METADATA_VERSION//-/ }) - POTENTIAL_MILESTONE=${METADATA_ARRAY[0]} - - echo "Will try to create changelog based on milestone ${POTENTIAL_MILESTONE} if it exists..." - - # In case the Jar below fails, fall back to a default text - echo "MAUTIC_CHANGELOG=\"**Based on Mautic's version number (${POTENTIAL_MILESTONE}), we tried finding a milestone with the same name. We couldn't find it though. Make sure it exists (and re-run GitHub Actions) or add a changelog manually!**\"" >> $GITHUB_ENV - - # Download changelog generator - wget -q https://github.com/spring-io/github-changelog-generator/releases/download/v0.0.5/github-changelog-generator.jar - - # Copy of release-notes.yml to root folder needed, since the Jar can't read from hidden folders anymore (bug): https://github.com/Decathlon/release-notes-generator-action/pull/21 - cp ./.github/release-notes.yml ./release-notes.yml - java -jar ./github-changelog-generator.jar --spring.config.location="./release-notes.yml" ${POTENTIAL_MILESTONE} mautic-changelog.txt || true - - if [[ -f mautic-changelog.txt ]]; then - echo 'MAUTIC_CHANGELOG<> $GITHUB_ENV - cat mautic-changelog.txt >> $GITHUB_ENV - echo 'EOF' >> $GITHUB_ENV - fi - - name: Create Release id: create_release uses: actions/create-release@v1 @@ -282,4 +257,4 @@ jobs: "${{ secrets.MAUTIC_INSTANCE_PASSWORD }}" \ "${{ env.MAUTIC_VERSION }}" \ 4 \ - "${{ github.workspace }}/${{ env.MAUTIC_VERSION }}.zip" + "${{ github.workspace }}/${{ env.MAUTIC_VERSION }}.zip" \ No newline at end of file diff --git a/.github/workflows/split-monorepo-in-multi-repo.yml b/.github/workflows/split-monorepo-in-multi-repo.yml index 849c71dd5ce..233072b5288 100644 --- a/.github/workflows/split-monorepo-in-multi-repo.yml +++ b/.github/workflows/split-monorepo-in-multi-repo.yml @@ -30,7 +30,7 @@ on: jobs: sync: - if: github.repository_owner == 'mautic' + if: github.repository == 'mautic/mautic' runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85ae8d07d8d..d9c672a4087 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,6 +55,13 @@ jobs: coverage: pcov ini-values: pcov.directory=., pcov.exclude="~tests|themes|vendor~" + - name: add MySQL config file + run: | + mysqldump --version + mysqldump --print-defaults + cp .github/ci-files/.my.cnf ~/.my.cnf + mysqldump --print-defaults + - name: Set SYMFONY_ENV to test run: echo "SYMFONY_ENV=test" >> $GITHUB_ENV @@ -63,17 +70,6 @@ jobs: composer validate composer install --prefer-dist --no-progress --no-suggest - - name: Temp install Mautic first due to failing tests - env: - mysql_port: ${{ job.services.mysql.ports[3306] }} - mariadb_port: ${{ job.services.mariadb.ports[3306] }} - run: | - export DB_PORT_STRING="${{ matrix.db-types }}_port" - export DB_PORT=${!DB_PORT_STRING} - - cp ./.github/ci-files/local.php ./app/config/local.php - php bin/console mautic:install --force http://localhost - - name: Run tests - database = ${{ matrix.db-types }} run: | export DB_PORT_STRING="${{ matrix.db-types }}_port" @@ -88,15 +84,8 @@ jobs: mysql_port: ${{ job.services.mysql.ports[3306] }} mariadb_port: ${{ job.services.mariadb.ports[3306] }} - - name: Run Pipedrive tests separately due to bug - run: | - export DB_PORT_STRING="${{ matrix.db-types }}_port" - export DB_PORT=${!DB_PORT_STRING} - - composer test -- plugins/MauticCrmBundle/Tests/Pipedrive - - name: Upload coverage report - if: ${{ matrix.php-versions == '7.4' && matrix.db-types == 'mysql' }} + if: ${{ matrix.php-versions == '7.4' && matrix.db-types == 'mysql' && github.repository == 'mautic/mautic' }} uses: codecov/codecov-action@v2 with: files: ./coverage.xml @@ -124,7 +113,7 @@ jobs: strategy: fail-fast: false matrix: - commands: ['PHPSTAN', 'CS Fixer', 'Rector'] + commands: ['PHPSTAN', 'CS Fixer', 'Rector', 'scaffolded files mismatch', 'PHPStan baseline changes'] name: ${{ matrix.commands }} @@ -163,6 +152,27 @@ jobs: if [[ $cs_fix_files ]]; then bin/php-cs-fixer fix --config=.php-cs-fixer.php -v --dry-run --using-cache=no --show-progress=dots --diff $cs_fix_files fi + elif [[ "${{ matrix.commands }}" == "scaffolded files mismatch" ]]; then + wget -q -O /tmp/jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 && chmod 755 /tmp/jq + /tmp/jq -r '.extra["mautic-scaffold"]["file-mapping"] | to_entries[] | "diff -q \(.key | sub("\\[(project|web)-root\\]";".")) app/\(.value)"' app/composer.json > diff_commands.sh + bash diff_commands.sh | tee /tmp/diff_command_output.txt + rm diff_commands.sh + if [[ $(wc -l - # Except those whitelisted bellow. - + # Except those allowed below. + Require all granted - + # Fallback for Apache < 2.4 @@ -129,10 +129,10 @@ Deny from all - # Except those whitelisted bellow. - + # Except those allowed below. + Order allow,deny Allow from all - + diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 3f6b63b6720..cbda53259a4 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -30,5 +30,8 @@ * (which is required for Symfony 5). */ 'no_alternative_syntax' => false, + 'header_comment' => [ + 'header' => '', + ], ]) ->setFinder($finder); diff --git a/README.md b/README.md index 48e6cee7f31..c5012830c51 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![codecov](https://codecov.io/gh/mautic/mautic/branch/features/graph/badge.svg)](https://codecov.io/gh/mautic/mautic) -[![All Contributors](https://img.shields.io/badge/all_contributors-73-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-74-orange.svg?style=flat-square)](#contributors-) About Mautic @@ -169,6 +169,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
nick-vanpraet

💻
Volha Pivavarchyk

📓
Nish Joseph

💻 +
Damzoneuh

💻 diff --git a/app/assets/scaffold/files/example.gitignore b/app/assets/scaffold/files/example.gitignore index 6a5aca69321..09ccad59ffb 100644 --- a/app/assets/scaffold/files/example.gitignore +++ b/app/assets/scaffold/files/example.gitignore @@ -4,7 +4,13 @@ !.htaccess !.gitkeep !.ddev +!.php-cs-fixer.php +!.github/ci-files/.my.cnf .ddev/mautic-preference +/.ddev/docker-compose.host-docker-internal.yaml +!.gitpod.Dockerfile +!.gitpod.yml +!.ddev/commands/web/dd .php_cs.cache /.env /composer.phar @@ -79,13 +85,29 @@ !/media/images/.htaccess /plugins/* +!/plugins/GrapesJsBuilderBundle +!/plugins/MauticCitrixBundle +!/plugins/MauticClearbitBundle +!/plugins/MauticCloudStorageBundle +!/plugins/MauticCrmBundle +!/plugins/MauticDynamicsBundle +!/plugins/MauticEmailMarketingBundle +!/plugins/MauticFocusBundle +!/plugins/MauticFullContactBundle +!/plugins/MauticGmailBundle +!/plugins/MauticOutlookBundle +!/plugins/MauticSocialBundle +!/plugins/MauticZapierBundle +!/plugins/MauticTagManagerBundle !/plugins/index.html +!/plugins/.gitkeep /themes/* !/themes/aurora !/themes/blank !/themes/cards !/themes/coffee +!/themes/confirmme !/themes/fresh-center !/themes/fresh-fixed !/themes/fresh-left diff --git a/app/assets/scaffold/files/htaccess b/app/assets/scaffold/files/htaccess index 4a0d9e8977b..a1ad665eed0 100644 --- a/app/assets/scaffold/files/htaccess +++ b/app/assets/scaffold/files/htaccess @@ -109,10 +109,10 @@ Require all denied - # Except those whitelisted bellow. - + # Except those allowed below. + Require all granted - + # Fallback for Apache < 2.4 @@ -129,10 +129,10 @@ Deny from all - # Except those whitelisted bellow. - + # Except those allowed below. + Order allow,deny Allow from all - + diff --git a/app/assets/scaffold/files/package-lock.json b/app/assets/scaffold/files/package-lock.json index 094c51aefd7..4aa4497f34c 100644 --- a/app/assets/scaffold/files/package-lock.json +++ b/app/assets/scaffold/files/package-lock.json @@ -1,13 +1,1799 @@ { "name": "mautic", "version": "0.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "mautic", + "version": "0.0.0", + "devDependencies": { + "grunt": "^0.4.5", + "grunt-autoprefixer": "^1.0.1", + "grunt-contrib-copy": "^0.5.0", + "grunt-contrib-cssmin": "^0.10.0", + "grunt-contrib-less": "^0.11.4", + "grunt-contrib-watch": "^0.6.1", + "grunt-remove": "^0.1.0", + "load-grunt-tasks": "^0.6.0", + "time-grunt": "^1.0.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "dependencies": { + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" + } + }, + "node_modules/argparse/node_modules/underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/array-differ": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-0.1.0.tgz", + "integrity": "sha1-EuLJtwa+1HyLSDtX5IdHP7CGHzo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-0.1.0.tgz", + "integrity": "sha1-7emAiDMGZeaZ4evwIny8YDTmJ9s=", + "dev": true, + "dependencies": { + "array-uniq": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-0.1.1.tgz", + "integrity": "sha1-WGHz7U5LthdVl6TgeOiqeOvpWMc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.9" + } + }, + "node_modules/assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true + }, + "node_modules/autoprefixer-core": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/autoprefixer-core/-/autoprefixer-core-3.1.2.tgz", + "integrity": "sha1-reXOni2dcbt//DHWlvpeh66+tjQ=", + "dev": true, + "dependencies": { + "caniuse-db": "^1.0.30000006", + "postcss": "~2.2.5" + } + }, + "node_modules/aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "dependencies": { + "hoek": "0.9.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "dependencies": { + "pako": "~0.2.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/caniuse-db": { + "version": "1.0.30001251", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001251.tgz", + "integrity": "sha512-qZcXjfDu3lwN6LJMpG0qI2Oz0IGpyXh5exkSeWOc3/I7dZBshplxOcRXGtcFIBTr6bCeBxvgQRxwkKTJOr6d1w==", + "dev": true + }, + "node_modules/chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "dependencies": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-css": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-2.2.23.tgz", + "integrity": "sha1-BZC1R4tRbEkD7cLYm9P9vdKGMow=", + "dev": true, + "dependencies": { + "commander": "2.2.x" + }, + "bin": { + "cleancss": "bin/cleancss" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/coffee-script": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", + "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)", + "dev": true, + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "optional": true, + "dependencies": { + "delayed-stream": "0.0.5" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.2.0.tgz", + "integrity": "sha1-F1rUuTF/P/YV8gHB5XIk9Vo+kd8=", + "dev": true, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "dependencies": { + "boom": "0.4.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/date-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-1.1.0.tgz", + "integrity": "sha1-GIdtC9pMGf5w3Tv0sDTygbEqQLY=", + "dev": true, + "dependencies": { + "time-zone": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dateformat": { + "version": "1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/deep-equal": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.0.0.tgz", + "integrity": "sha1-mWedO70EcVb81FDT0B7rkGhpHoM=", + "dev": true + }, + "node_modules/defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", + "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/faye-websocket": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.4.4.tgz", + "integrity": "sha1-wUxbO/FNdBf/v9mQwKdJXNnzN7w=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, + "dependencies": { + "glob": "~3.2.9", + "lodash": "~2.4.1" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/findup-sync/node_modules/glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "dependencies": { + "inherits": "2", + "minimatch": "0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/findup-sync/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/findup-sync/node_modules/lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/findup-sync/node_modules/minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "optional": true, + "dependencies": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime": "~1.2.11" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/form-data/node_modules/async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true, + "optional": true + }, + "node_modules/gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "dev": true, + "dependencies": { + "globule": "~0.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "dependencies": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "dev": true, + "dependencies": { + "glob": "~3.1.21", + "lodash": "~1.0.1", + "minimatch": "~0.2.11" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/globule/node_modules/lodash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "deprecated": "please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/grunt": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "dependencies": { + "async": "~0.1.22", + "coffee-script": "~1.3.3", + "colors": "~0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.1.2", + "getobject": "~0.1.0", + "glob": "~3.1.21", + "grunt-legacy-log": "~0.1.0", + "grunt-legacy-util": "~0.2.0", + "hooker": "~0.2.3", + "iconv-lite": "~0.2.11", + "js-yaml": "~2.0.5", + "lodash": "~0.9.2", + "minimatch": "~0.2.12", + "nopt": "~1.0.10", + "rimraf": "~2.2.8", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-autoprefixer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt-autoprefixer/-/grunt-autoprefixer-1.0.1.tgz", + "integrity": "sha1-TQ2wR+SnSI1x5b2pihhDWu4LREY=", + "dev": true, + "dependencies": { + "autoprefixer-core": "^3.0.0", + "chalk": "~0.5.0", + "diff": "~1.0.8" + }, + "engines": { + "node": ">= 0.10.0" + }, + "peerDependencies": { + "grunt": "~0.4.2" + } + }, + "node_modules/grunt-contrib-copy": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-0.5.0.tgz", + "integrity": "sha1-QQB1rEWlhWuhkbHMclclRQ1KAhU=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + }, + "peerDependencies": { + "grunt": "~0.4.0" + } + }, + "node_modules/grunt-contrib-cssmin": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-0.10.0.tgz", + "integrity": "sha1-4F80HnU6lnSysQcCIP3LrCIHlBg=", + "dev": true, + "dependencies": { + "chalk": "~0.4.0", + "clean-css": "~2.2.0", + "maxmin": "~0.2.0" + }, + "engines": { + "node": ">= 0.8.0" + }, + "peerDependencies": { + "grunt": "~0.4.1" + } + }, + "node_modules/grunt-contrib-cssmin/node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-cssmin/node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-cssmin/node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-less": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/grunt-contrib-less/-/grunt-contrib-less-0.11.4.tgz", + "integrity": "sha1-VmdHWsRRfzLKYjuaTYHWz0rtK1E=", + "dev": true, + "dependencies": { + "async": "^0.2.10", + "chalk": "^0.5.1", + "less": "^1.7.2", + "lodash": "^2.4.1", + "maxmin": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "grunt": "~0.4.0" + } + }, + "node_modules/grunt-contrib-less/node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-less/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "node_modules/grunt-contrib-less/node_modules/gzip-size": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-0.1.1.tgz", + "integrity": "sha1-rjNIO2/IIk6DQilt4Qjvk3V/duA=", + "dev": true, + "dependencies": { + "concat-stream": "^1.4.1", + "zlib-browserify": "^0.0.3" + }, + "bin": { + "gzip-size": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-less/node_modules/lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/grunt-contrib-less/node_modules/maxmin": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-0.1.0.tgz", + "integrity": "sha1-ldgcUonjqdMPf8fcVZwCTlAwydA=", + "dev": true, + "dependencies": { + "chalk": "^0.4.0", + "gzip-size": "^0.1.0", + "pretty-bytes": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/grunt-contrib-less/node_modules/maxmin/node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-less/node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/grunt-contrib-watch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-0.6.1.tgz", + "integrity": "sha1-ZP3LolpjX1tNobbOb5DaCutuPxU=", + "dev": true, + "dependencies": { + "async": "~0.2.9", + "gaze": "~0.5.1", + "lodash": "~2.4.1", + "tiny-lr-fork": "0.0.5" + }, + "engines": { + "node": ">= 0.8.0" + }, + "peerDependencies": { + "grunt": "~0.4.0" + } + }, + "node_modules/grunt-contrib-watch/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "node_modules/grunt-contrib-watch/node_modules/lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/grunt-legacy-log": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "dependencies": { + "colors": "~0.6.2", + "grunt-legacy-log-utils": "~0.1.1", + "hooker": "~0.2.3", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-legacy-log-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "dependencies": { + "colors": "~0.6.2", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-legacy-log-utils/node_modules/lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/grunt-legacy-log-utils/node_modules/underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/grunt-legacy-log/node_modules/lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/grunt-legacy-log/node_modules/underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/grunt-legacy-util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "dependencies": { + "async": "~0.1.22", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~0.9.2", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/grunt-remove": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/grunt-remove/-/grunt-remove-0.1.0.tgz", + "integrity": "sha1-OUO2QhdtrQSgbA3FYC6zs99PvUo=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + }, + "peerDependencies": { + "grunt": "~0.4.1" + } + }, + "node_modules/gzip-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-0.2.0.tgz", + "integrity": "sha1-46KhkSBf5W7jJvXCcUNd+uz7Phw=", + "dev": true, + "dependencies": { + "browserify-zlib": "^0.1.4", + "concat-stream": "^1.4.1" + }, + "bin": { + "gzip-size": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "dependencies": { + "ansi-regex": "^0.2.0" + }, + "bin": { + "has-ansi": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dev": true, + "optional": true, + "dependencies": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "optional": true, + "dependencies": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "node_modules/is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/js-base64": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", + "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "dependencies": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + }, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "node_modules/less": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/less/-/less-1.7.5.tgz", + "integrity": "sha1-TyIM9yiKJ+rKc5325ICKLUwNV1Y=", + "dev": true, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "clean-css": "2.2.x", + "graceful-fs": "~3.0.2", + "mime": "~1.2.11", + "mkdirp": "~0.5.0", + "request": "~2.40.0", + "source-map": "0.1.x" + } + }, + "node_modules/less/node_modules/graceful-fs": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", + "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", + "dev": true, + "optional": true, + "dependencies": { + "natives": "^1.1.3" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/load-grunt-tasks": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-0.6.0.tgz", + "integrity": "sha1-BDwErWnsyF4CqCJY/fJbenng22w=", + "dev": true, + "dependencies": { + "findup-sync": "^0.1.2", + "multimatch": "^0.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true, + "engines": [ + "node", + "rhino" + ] + }, + "node_modules/lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "node_modules/maxmin": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-0.2.2.tgz", + "integrity": "sha1-o2ztjMIuOrzRCM+3l6OktAJ1WT8=", + "dev": true, + "dependencies": { + "chalk": "^0.5.0", + "figures": "^1.0.1", + "gzip-size": "^0.2.0", + "pretty-bytes": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true, + "optional": true + }, + "node_modules/mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true, + "optional": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "optional": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-0.3.0.tgz", + "integrity": "sha1-YD28P+MoHTOAlKHhuTqLXyvgONo=", + "dev": true, + "dependencies": { + "array-differ": "^0.1.0", + "array-union": "^0.1.0", + "minimatch": "^0.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/multimatch/node_modules/minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", + "dev": true, + "dependencies": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/natives": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", + "deprecated": "This module relies on Node.js's internals and will break at some point. Do not use it, and update to graceful-fs@4.x.", + "dev": true, + "optional": true + }, + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "deprecated": "Use uuid module instead", + "dev": true, + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/noptify": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/noptify/-/noptify-0.0.3.tgz", + "integrity": "sha1-WPZUpz2XU98MUdlobckhBKZ/S7s=", + "dev": true, + "dependencies": { + "nopt": "~2.0.0" + } + }, + "node_modules/noptify/node_modules/nopt": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.0.0.tgz", + "integrity": "sha1-ynQW8gpeP5w7hhgPlilfo9C1Lg0=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "node_modules/parse-ms": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", + "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plur": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", + "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-2.2.6.tgz", + "integrity": "sha1-wENE4kSeRYa5Vfvkp093CA2EVx8=", + "dev": true, + "dependencies": { + "js-base64": "~2.1.5", + "source-map": "~0.1.40" + } + }, + "node_modules/pretty-bytes": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-0.1.2.tgz", + "integrity": "sha1-zZApTVihyk6KXQ+5yCJZmIgazwA=", + "dev": true, + "bin": { + "pretty-bytes": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", + "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=", + "dev": true, + "dependencies": { + "is-finite": "^1.0.1", + "parse-ms": "^1.0.0", + "plur": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true, + "optional": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz", + "integrity": "sha1-UKk+K1r2aRwxvOpdrnjubqGQN2g=", + "dev": true, + "optional": true + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/request": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz", + "integrity": "sha1-TdZw9pbx5uhC5mtLXoOTAaub62c=", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "optional": true, + "dependencies": { + "forever-agent": "~0.5.0", + "json-stringify-safe": "~5.0.0", + "mime-types": "~1.0.1", + "node-uuid": "~1.4.0", + "qs": "~1.0.0" + }, + "optionalDependencies": { + "aws-sign2": "~0.5.0", + "form-data": "~0.1.0", + "hawk": "1.1.1", + "http-signature": "~0.10.0", + "oauth-sign": "~0.3.0", + "stringstream": "~0.0.4", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "node_modules/rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "node_modules/sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "deprecated": "This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dev": true, + "optional": true, + "dependencies": { + "hoek": "0.9.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "dev": true, + "optional": true + }, + "node_modules/strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "dependencies": { + "ansi-regex": "^0.2.1" + }, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true, + "bin": { + "supports-color": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tape": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/tape/-/tape-0.2.2.tgz", + "integrity": "sha1-ZMz6S37PSgBgAH5hcW1CR4FnFjc=", + "dev": true, + "dependencies": { + "deep-equal": "~0.0.0", + "defined": "~0.0.0", + "jsonify": "~0.0.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/time-grunt": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/time-grunt/-/time-grunt-1.4.0.tgz", + "integrity": "sha1-BiIT5mDJB+hvRAVWwB6mWXtxJCA=", + "dev": true, + "dependencies": { + "chalk": "^1.0.0", + "date-time": "^1.1.0", + "figures": "^1.0.0", + "hooker": "^0.2.3", + "number-is-nan": "^1.0.0", + "pretty-ms": "^2.1.0", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/time-grunt/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/time-grunt/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/time-grunt/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/time-grunt/node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/time-grunt/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/time-grunt/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/time-zone": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-0.1.0.tgz", + "integrity": "sha1-Sncotqwo2w4Aj1FAQ/1VW9VXO0Y=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-lr-fork": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/tiny-lr-fork/-/tiny-lr-fork-0.0.5.tgz", + "integrity": "sha1-Hpnh4qhGm3NquX2X7vqYxx927Qo=", + "dev": true, + "dependencies": { + "debug": "~0.7.0", + "faye-websocket": "~0.4.3", + "noptify": "~0.0.3", + "qs": "~0.5.2" + }, + "bin": { + "tiny-lr-fork": "bin/tiny-lr" + } + }, + "node_modules/tiny-lr-fork/node_modules/qs": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz", + "integrity": "sha1-MbGtBYVnZRxSaSFQa5qHk5EaA4Q=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "optional": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "node_modules/underscore.string": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true, + "bin": { + "which": "bin/which" + } + }, + "node_modules/zlib-browserify": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/zlib-browserify/-/zlib-browserify-0.0.3.tgz", + "integrity": "sha1-JAzNv9AgP6hCsTDe77FBQSLIzFA=", + "dev": true, + "dependencies": { + "tape": "~0.2.2" + } + } + }, "dependencies": { "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", "dev": true }, "argparse": { @@ -16,8 +1802,8 @@ "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", "dev": true, "requires": { - "underscore": "1.7.0", - "underscore.string": "2.4.0" + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" }, "dependencies": { "underscore.string": { @@ -28,12 +1814,117 @@ } } }, + "array-differ": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-0.1.0.tgz", + "integrity": "sha1-EuLJtwa+1HyLSDtX5IdHP7CGHzo=", + "dev": true + }, + "array-union": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-0.1.0.tgz", + "integrity": "sha1-7emAiDMGZeaZ4evwIny8YDTmJ9s=", + "dev": true, + "requires": { + "array-uniq": "^0.1.0" + } + }, + "array-uniq": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-0.1.1.tgz", + "integrity": "sha1-WGHz7U5LthdVl6TgeOiqeOvpWMc=", + "dev": true + }, + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true, + "optional": true + }, "async": { "version": "0.1.22", "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", "dev": true }, + "autoprefixer-core": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/autoprefixer-core/-/autoprefixer-core-3.1.2.tgz", + "integrity": "sha1-reXOni2dcbt//DHWlvpeh66+tjQ=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000006", + "postcss": "~2.2.5" + } + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true, + "optional": true + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "dev": true, + "optional": true, + "requires": { + "hoek": "0.9.x" + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "caniuse-db": { + "version": "1.0.30001251", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001251.tgz", + "integrity": "sha512-qZcXjfDu3lwN6LJMpG0qI2Oz0IGpyXh5exkSeWOc3/I7dZBshplxOcRXGtcFIBTr6bCeBxvgQRxwkKTJOr6d1w==", + "dev": true + }, + "chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "requires": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + } + }, + "clean-css": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-2.2.23.tgz", + "integrity": "sha1-BZC1R4tRbEkD7cLYm9P9vdKGMow=", + "dev": true, + "requires": { + "commander": "2.2.x" + } + }, "coffee-script": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", @@ -46,12 +1937,117 @@ "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", "dev": true }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "commander": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.2.0.tgz", + "integrity": "sha1-F1rUuTF/P/YV8gHB5XIk9Vo+kd8=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "dev": true, + "optional": true, + "requires": { + "boom": "0.4.x" + } + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true, + "optional": true + }, + "date-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/date-time/-/date-time-1.1.0.tgz", + "integrity": "sha1-GIdtC9pMGf5w3Tv0sDTygbEqQLY=", + "dev": true, + "requires": { + "time-zone": "^0.1.0" + } + }, "dateformat": { "version": "1.0.2-1.2.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", "dev": true }, + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", + "dev": true + }, + "deep-equal": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.0.0.tgz", + "integrity": "sha1-mWedO70EcVb81FDT0B7rkGhpHoM=", + "dev": true + }, + "defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true, + "optional": true + }, + "diff": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", + "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, "esprima": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", @@ -70,14 +2066,30 @@ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, + "faye-websocket": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.4.4.tgz", + "integrity": "sha1-wUxbO/FNdBf/v9mQwKdJXNnzN7w=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, "findup-sync": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", "dev": true, "requires": { - "glob": "3.2.11", - "lodash": "2.4.2" + "glob": "~3.2.9", + "lodash": "~2.4.1" }, "dependencies": { "glob": { @@ -86,10 +2098,16 @@ "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", "dev": true, "requires": { - "inherits": "2.0.3", - "minimatch": "0.3.0" + "inherits": "2", + "minimatch": "0.3" } }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "lodash": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", @@ -102,82 +2120,283 @@ "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", "dev": true, "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", + "dev": true, + "optional": true, + "requires": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime": "~1.2.11" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true, + "optional": true + } + } + }, + "gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "dev": true, + "requires": { + "globule": "~0.1.0" + } + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + } + }, + "globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "dev": true, + "requires": { + "glob": "~3.1.21", + "lodash": "~1.0.1", + "minimatch": "~0.2.11" + }, + "dependencies": { + "lodash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "grunt": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "requires": { + "async": "~0.1.22", + "coffee-script": "~1.3.3", + "colors": "~0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.1.2", + "getobject": "~0.1.0", + "glob": "~3.1.21", + "grunt-legacy-log": "~0.1.0", + "grunt-legacy-util": "~0.2.0", + "hooker": "~0.2.3", + "iconv-lite": "~0.2.11", + "js-yaml": "~2.0.5", + "lodash": "~0.9.2", + "minimatch": "~0.2.12", + "nopt": "~1.0.10", + "rimraf": "~2.2.8", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + } + }, + "grunt-autoprefixer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt-autoprefixer/-/grunt-autoprefixer-1.0.1.tgz", + "integrity": "sha1-TQ2wR+SnSI1x5b2pihhDWu4LREY=", + "dev": true, + "requires": { + "autoprefixer-core": "^3.0.0", + "chalk": "~0.5.0", + "diff": "~1.0.8" + } + }, + "grunt-contrib-copy": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-0.5.0.tgz", + "integrity": "sha1-QQB1rEWlhWuhkbHMclclRQ1KAhU=", + "dev": true, + "requires": {} + }, + "grunt-contrib-cssmin": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-0.10.0.tgz", + "integrity": "sha1-4F80HnU6lnSysQcCIP3LrCIHlBg=", + "dev": true, + "requires": { + "chalk": "~0.4.0", + "clean-css": "~2.2.0", + "maxmin": "~0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true + }, + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "requires": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + } + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true + } + } + }, + "grunt-contrib-less": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/grunt-contrib-less/-/grunt-contrib-less-0.11.4.tgz", + "integrity": "sha1-VmdHWsRRfzLKYjuaTYHWz0rtK1E=", + "dev": true, + "requires": { + "async": "^0.2.10", + "chalk": "^0.5.1", + "less": "^1.7.2", + "lodash": "^2.4.1", + "maxmin": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true + }, + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "gzip-size": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-0.1.1.tgz", + "integrity": "sha1-rjNIO2/IIk6DQilt4Qjvk3V/duA=", + "dev": true, + "requires": { + "concat-stream": "^1.4.1", + "zlib-browserify": "^0.0.3" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "maxmin": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-0.1.0.tgz", + "integrity": "sha1-ldgcUonjqdMPf8fcVZwCTlAwydA=", + "dev": true, + "requires": { + "chalk": "^0.4.0", + "gzip-size": "^0.1.0", + "pretty-bytes": "^0.1.0" + }, + "dependencies": { + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "requires": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + } + } } + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true } } }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "grunt-contrib-watch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-0.6.1.tgz", + "integrity": "sha1-ZP3LolpjX1tNobbOb5DaCutuPxU=", "dev": true, "requires": { - "graceful-fs": "1.2.3", - "inherits": "1.0.2", - "minimatch": "0.2.14" + "async": "~0.2.9", + "gaze": "~0.5.1", + "lodash": "~2.4.1", + "tiny-lr-fork": "0.0.5" }, "dependencies": { - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", + "dev": true + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", "dev": true } } }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "grunt": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", - "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", - "dev": true, - "requires": { - "async": "0.1.22", - "coffee-script": "1.3.3", - "colors": "0.6.2", - "dateformat": "1.0.2-1.2.3", - "eventemitter2": "0.4.14", - "exit": "0.1.2", - "findup-sync": "0.1.3", - "getobject": "0.1.0", - "glob": "3.1.21", - "grunt-legacy-log": "0.1.3", - "grunt-legacy-util": "0.2.0", - "hooker": "0.2.3", - "iconv-lite": "0.2.11", - "js-yaml": "2.0.5", - "lodash": "0.9.2", - "minimatch": "0.2.14", - "nopt": "1.0.10", - "rimraf": "2.2.8", - "underscore.string": "2.2.1", - "which": "1.0.9" - } - }, "grunt-legacy-log": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", "dev": true, "requires": { - "colors": "0.6.2", - "grunt-legacy-log-utils": "0.1.1", - "hooker": "0.2.3", - "lodash": "2.4.2", - "underscore.string": "2.3.3" + "colors": "~0.6.2", + "grunt-legacy-log-utils": "~0.1.1", + "hooker": "~0.2.3", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" }, "dependencies": { "lodash": { @@ -200,9 +2419,9 @@ "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", "dev": true, "requires": { - "colors": "0.6.2", - "lodash": "2.4.2", - "underscore.string": "2.3.3" + "colors": "~0.6.2", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" }, "dependencies": { "lodash": { @@ -225,21 +2444,85 @@ "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", "dev": true, "requires": { - "async": "0.1.22", - "exit": "0.1.2", - "getobject": "0.1.0", - "hooker": "0.2.3", - "lodash": "0.9.2", - "underscore.string": "2.2.1", - "which": "1.0.9" + "async": "~0.1.22", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~0.9.2", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + } + }, + "grunt-remove": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/grunt-remove/-/grunt-remove-0.1.0.tgz", + "integrity": "sha1-OUO2QhdtrQSgbA3FYC6zs99PvUo=", + "dev": true, + "requires": {} + }, + "gzip-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-0.2.0.tgz", + "integrity": "sha1-46KhkSBf5W7jJvXCcUNd+uz7Phw=", + "dev": true, + "requires": { + "browserify-zlib": "^0.1.4", + "concat-stream": "^1.4.1" + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.0" + } + }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true + }, + "hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "dev": true, + "optional": true, + "requires": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" } }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "dev": true, + "optional": true + }, "hooker": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", "dev": true }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "optional": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + } + }, "iconv-lite": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", @@ -247,9 +2530,27 @@ "dev": true }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "js-base64": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", + "integrity": "sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4=", "dev": true }, "js-yaml": { @@ -258,8 +2559,57 @@ "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", "dev": true, "requires": { - "argparse": "0.1.16", - "esprima": "1.0.4" + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "less": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/less/-/less-1.7.5.tgz", + "integrity": "sha1-TyIM9yiKJ+rKc5325ICKLUwNV1Y=", + "dev": true, + "requires": { + "clean-css": "2.2.x", + "graceful-fs": "~3.0.2", + "mime": "~1.2.11", + "mkdirp": "~0.5.0", + "request": "~2.40.0", + "source-map": "0.1.x" + }, + "dependencies": { + "graceful-fs": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", + "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", + "dev": true, + "optional": true, + "requires": { + "natives": "^1.1.3" + } + } + } + }, + "load-grunt-tasks": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-0.6.0.tgz", + "integrity": "sha1-BDwErWnsyF4CqCJY/fJbenng22w=", + "dev": true, + "requires": { + "findup-sync": "^0.1.2", + "multimatch": "^0.3.0" } }, "lodash": { @@ -274,23 +2624,259 @@ "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", "dev": true }, + "maxmin": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-0.2.2.tgz", + "integrity": "sha1-o2ztjMIuOrzRCM+3l6OktAJ1WT8=", + "dev": true, + "requires": { + "chalk": "^0.5.0", + "figures": "^1.0.1", + "gzip-size": "^0.2.0", + "pretty-bytes": "^0.1.0" + } + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true, + "optional": true + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "dev": true, + "optional": true + }, "minimatch": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", "dev": true, "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true, + "optional": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "multimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-0.3.0.tgz", + "integrity": "sha1-YD28P+MoHTOAlKHhuTqLXyvgONo=", + "dev": true, + "requires": { + "array-differ": "^0.1.0", + "array-union": "^0.1.0", + "minimatch": "^0.3.0" + }, + "dependencies": { + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } } }, + "natives": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", + "dev": true, + "optional": true + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true, + "optional": true + }, "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "1.1.0" + "abbrev": "1" + } + }, + "noptify": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/noptify/-/noptify-0.0.3.tgz", + "integrity": "sha1-WPZUpz2XU98MUdlobckhBKZ/S7s=", + "dev": true, + "requires": { + "nopt": "~2.0.0" + }, + "dependencies": { + "nopt": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.0.0.tgz", + "integrity": "sha1-ynQW8gpeP5w7hhgPlilfo9C1Lg0=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parse-ms": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", + "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", + "dev": true + }, + "plur": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", + "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", + "dev": true + }, + "postcss": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-2.2.6.tgz", + "integrity": "sha1-wENE4kSeRYa5Vfvkp093CA2EVx8=", + "dev": true, + "requires": { + "js-base64": "~2.1.5", + "source-map": "~0.1.40" + } + }, + "pretty-bytes": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-0.1.2.tgz", + "integrity": "sha1-zZApTVihyk6KXQ+5yCJZmIgazwA=", + "dev": true + }, + "pretty-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", + "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=", + "dev": true, + "requires": { + "is-finite": "^1.0.1", + "parse-ms": "^1.0.0", + "plur": "^1.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true, + "optional": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "optional": true + }, + "qs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz", + "integrity": "sha1-UKk+K1r2aRwxvOpdrnjubqGQN2g=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } + } + }, + "request": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz", + "integrity": "sha1-TdZw9pbx5uhC5mtLXoOTAaub62c=", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "~0.5.0", + "forever-agent": "~0.5.0", + "form-data": "~0.1.0", + "hawk": "1.1.1", + "http-signature": "~0.10.0", + "json-stringify-safe": "~5.0.0", + "mime-types": "~1.0.1", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.3.0", + "qs": "~1.0.0", + "stringstream": "~0.0.4", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" } }, "rimraf": { @@ -299,12 +2885,202 @@ "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", "dev": true }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", "dev": true }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "dev": true, + "optional": true, + "requires": { + "hoek": "0.9.x" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.1" + } + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "tape": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/tape/-/tape-0.2.2.tgz", + "integrity": "sha1-ZMz6S37PSgBgAH5hcW1CR4FnFjc=", + "dev": true, + "requires": { + "deep-equal": "~0.0.0", + "defined": "~0.0.0", + "jsonify": "~0.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "time-grunt": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/time-grunt/-/time-grunt-1.4.0.tgz", + "integrity": "sha1-BiIT5mDJB+hvRAVWwB6mWXtxJCA=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "date-time": "^1.1.0", + "figures": "^1.0.0", + "hooker": "^0.2.3", + "number-is-nan": "^1.0.0", + "pretty-ms": "^2.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "time-zone": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-0.1.0.tgz", + "integrity": "sha1-Sncotqwo2w4Aj1FAQ/1VW9VXO0Y=", + "dev": true + }, + "tiny-lr-fork": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/tiny-lr-fork/-/tiny-lr-fork-0.0.5.tgz", + "integrity": "sha1-Hpnh4qhGm3NquX2X7vqYxx927Qo=", + "dev": true, + "requires": { + "debug": "~0.7.0", + "faye-websocket": "~0.4.3", + "noptify": "~0.0.3", + "qs": "~0.5.2" + }, + "dependencies": { + "qs": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz", + "integrity": "sha1-MbGtBYVnZRxSaSFQa5qHk5EaA4Q=", + "dev": true + } + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "optional": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "underscore": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", @@ -317,11 +3093,33 @@ "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", "dev": true }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "which": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", "dev": true + }, + "zlib-browserify": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/zlib-browserify/-/zlib-browserify-0.0.3.tgz", + "integrity": "sha1-JAzNv9AgP6hCsTDe77FBQSLIzFA=", + "dev": true, + "requires": { + "tape": "~0.2.2" + } } } } diff --git a/app/assets/scaffold/files/upgrade.php b/app/assets/scaffold/files/upgrade.php index ed3068adc2e..76698353584 100644 --- a/app/assets/scaffold/files/upgrade.php +++ b/app/assets/scaffold/files/upgrade.php @@ -1,18 +1,10 @@ false, 'error' => false, 'updateState' => $state, 'stepStatus' => 'In Progress']; -// Web request upgrade -if (!IN_CLI) { - $request = explode('?', $_SERVER['REQUEST_URI'])[0]; - $url = "//{$_SERVER['HTTP_HOST']}{$request}"; - $isSSL = (!empty($_SERVER['HTTPS']) && 'off' != $_SERVER['HTTPS']); - $cookie_path = (isset($localParameters['cookie_path'])) ? $localParameters['cookie_path'] : '/'; - $cookie_domain = (isset($localParameters['cookie_domain'])) ? $localParameters['cookie_domain'] : ''; - $cookie_secure = (isset($localParameters['cookie_secure'])) ? $localParameters['cookie_secure'] : $isSSL; - $cookie_httponly = (isset($localParameters['cookie_httponly'])) ? $localParameters['cookie_httponly'] : false; - - setcookie('mautic_update', $task, time() + 300, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly); - $query = ''; - $maxCount = (!empty($standalone)) ? 25 : 5; - - switch ($task) { - case '': - html_body("

Click here to start upgrade.


Do not refresh or stop the process. This may take several minutes.
"); - - // no break - case 'startUpgrade': - $nextTask = 'fetchUpdates'; - break; - - case 'fetchUpdates': - [$success, $message] = fetch_updates(); - - if (!$success) { - html_body("
$message
"); - } - - $query = "version=$message&"; - $nextTask = 'extractUpdate'; - break; - - case 'extractUpdate': - [$success, $message] = extract_package(getVar('version')); +if (IN_CLI) { + echo "Upgrading through upgrade.php using the CLI is no longer supported. Please use 'php bin/console mautic:update:find' instead. \n"; + exit(1); +} - if (!$success) { - html_body("
$message
"); +// Web request upgrade +$request = explode('?', $_SERVER['REQUEST_URI'])[0]; +$url = "//{$_SERVER['HTTP_HOST']}{$request}"; +$isSSL = (!empty($_SERVER['HTTPS']) && 'off' != $_SERVER['HTTPS']); +$cookie_path = (isset($localParameters['cookie_path'])) ? $localParameters['cookie_path'] : '/'; +$cookie_domain = (isset($localParameters['cookie_domain'])) ? $localParameters['cookie_domain'] : ''; +$cookie_secure = (isset($localParameters['cookie_secure'])) ? $localParameters['cookie_secure'] : $isSSL; +$cookie_httponly = (isset($localParameters['cookie_httponly'])) ? $localParameters['cookie_httponly'] : false; + +setcookie('mautic_update', $task, time() + 300, $cookie_path, $cookie_domain, $cookie_secure, $cookie_httponly); +$query = ''; +$maxCount = (!empty($standalone)) ? 25 : 5; + +switch ($task) { + case '': + html_body("
This script cannot run standalone. Please log into Mautic to check for updates.
"); + + // no break + case 'moveBundles': + $status = move_mautic_bundles($status, $maxCount); + if (empty($status['complete'])) { + if (!isset($state['refresh_count'])) { + $state['refresh_count'] = 1; } - $nextTask = 'moveBundles'; - break; - - case 'moveBundles': - $status = move_mautic_bundles($status, $maxCount); - if (empty($status['complete'])) { - if (!isset($state['refresh_count'])) { - $state['refresh_count'] = 1; - } - $nextTask = 'moveBundles'; - $query = 'count='.$state['refresh_count'].'&'; - ++$state['refresh_count']; - } else { - $nextTask = 'moveCore'; - unset($state['refresh_count']); - } - break; - - case 'moveCore': - $status = move_mautic_core($status); - $nextTask = 'moveVendors'; - break; - - case 'moveVendors': - $status = move_mautic_vendors($status, $maxCount); - $nextTask = (!empty($status['complete'])) ? 'clearCache' : 'moveVendors'; + $query = 'count='.$state['refresh_count'].'&'; + ++$state['refresh_count']; + } else { + $nextTask = 'moveCore'; + unset($state['refresh_count']); + } + break; - if (empty($status['complete'])) { - if (!isset($state['refresh_count'])) { - $state['refresh_count'] = 1; - } - $nextTask = 'moveVendors'; - $query = 'count='.$state['refresh_count'].'&'; - ++$state['refresh_count']; - } else { - $nextTask = 'clearCache'; - unset($state['refresh_count']); - } - break; - - case 'clearCache': - clear_mautic_cache(); - $nextTask = 'buildCache'; - $redirect = true; - break; - - case 'buildCache': - build_cache(); - $nextTask = (!empty($standalone)) ? 'applyMigrations' : 'applyCriticalMigrations'; - $redirect = true; - break; - - case 'applyCriticalMigrations': - // Apply critical migrations - apply_critical_migrations(); - $nextTask = 'finish'; - $redirect = true; - break; - - case 'applyMigrations': - // Apply critical migrations - apply_migrations(); - $nextTask = 'finish'; - - break; - - case 'finish': - clear_mautic_cache(); - - if (!empty($standalone)) { - html_body("

Success!

Remove this script!

"); - } else { - $status['complete'] = true; - $status['stepStatus'] = 'Success'; - $status['nextStep'] = 'Processing Database Updates'; - $status['nextStepStatus'] = 'In Progress'; - $status['updateState']['cacheComplete'] = true; - } - break; + case 'moveCore': + $status = move_mautic_core($status); + $nextTask = 'moveVendors'; + break; - default: - $status['error'] = true; - $status['message'] = 'Invalid task'; - $status['stepStatus'] = 'Failed'; - break; - } + case 'moveVendors': + $status = move_mautic_vendors($status, $maxCount); + $nextTask = (!empty($status['complete'])) ? 'clearCache' : 'moveVendors'; - if ($standalone || !empty($redirect)) { - // Standalone updater or redirecting to help prevent timeouts - if (!empty($nextTask)) { - if ('finish' == $nextTask) { - header("Location: $url?task=$nextTask&standalone=$standalone"); - } else { - header("Location: $url?{$query}task=$nextTask&standalone=$standalone&updateState=".get_state_param($state)); + if (empty($status['complete'])) { + if (!isset($state['refresh_count'])) { + $state['refresh_count'] = 1; } - - exit; + $nextTask = 'moveVendors'; + $query = 'count='.$state['refresh_count'].'&'; + ++$state['refresh_count']; + } else { + $nextTask = 'clearCache'; + unset($state['refresh_count']); } - } else { - // Request through Mautic's UI - $status['updateState'] = get_state_param($status['updateState']); - - send_response($status); - } -} else { - // CLI upgrade - echo 'Checking for new updates...'; - [$success, $message] = fetch_updates(); - if (!$success) { - echo "failed. $message"; - exit; - } - $version = $message; - echo "updating to $version!\n"; - - echo 'Extracting the update package...'; - [$success, $message] = extract_package($version); - if (!$success) { - echo "failed. $message"; - exit; - } - echo "done!\n"; - - echo 'Moving files...'; - $status = move_mautic_bundles($status, -1); - $status = move_mautic_core($status); - $status = move_mautic_vendors($status, -1); - if (empty($status['complete'])) { - echo 'failed. Review udpate errors log for details.'; - exit; - } - unset($status['complete']); - echo "done!\n"; - - echo 'Clearing the cache...'; - if (!clear_mautic_cache()) { - echo 'failed. Review udpate errors log for details.'; - exit; - } - echo "done!\n"; + break; + + case 'clearCache': + clear_mautic_cache(); + $nextTask = 'finish'; + break; + + case 'finish': + $status['complete'] = true; + $status['stepStatus'] = 'Success'; + $status['nextStep'] = 'Processing Database Updates'; + $status['nextStepStatus'] = 'In Progress'; + $status['updateState']['cacheComplete'] = true; + + break; + + default: + $status['error'] = true; + $status['message'] = 'Invalid task'; + $status['stepStatus'] = 'Failed'; + break; +} - echo 'Rebuilding the cache...'; - if (!build_cache()) { - echo 'failed. Review udpate errors log for details.'; - exit; - } - echo "done!\n"; +// Request through Mautic's UI +$status['updateState'] = get_state_param($status['updateState']); - echo 'Applying migrations...'; - if (!apply_migrations()) { - echo 'failed. Review udpate errors log for details.'; - exit; - } - echo "done!\n"; - - echo 'Cleaning up...'; - if (!recursive_remove_directory(MAUTIC_UPGRADE_ROOT)) { - echo "failed. Manually delete the upgrade folder.\n"; - } - if (!clear_mautic_cache()) { - echo 'failed. Manually delete app/cache/prod.'; - } - echo "done!\n"; - - echo "\nSuccess!"; -} +send_response($status); /** * Get local parameters. @@ -338,118 +208,6 @@ function get_local_config() return $parameters; } -/** - * Fetch a list of updates. - * - * @return array - */ -function fetch_updates() -{ - global $localParameters; - - $version = file_get_contents(__DIR__.'/app/version.txt'); - try { - // Generate a unique instance ID for the site - $instanceId = hash('sha1', $localParameters['secret_key'].'Mautic'.$localParameters['db_driver']); - - $data = [ - 'application' => 'Mautic', - 'version' => $version, - 'phpVersion' => PHP_VERSION, - 'dbDriver' => $localParameters['db_driver'], - 'serverOs' => php_uname('s').' '.php_uname('r'), - 'instanceId' => $instanceId, - 'installSource' => (isset($localParameters['install_source'])) ? $localParameters['install_source'] : 'Mautic', - ]; - - make_request('https://updates.mautic.org/stats/send', 'post', $data); - } catch (\Exception $exception) { - // Not so concerned about failures here, move along - } - - // Get the update data - try { - $appData = [ - 'appVersion' => $version, - 'phpVersion' => PHP_VERSION, - 'stability' => (isset($localParameters['update_stability'])) ? $localParameters['update_stability'] : 'stable', - ]; - - $data = make_request('https://updates.mautic.org/index.php?option=com_mauticdownload&task=checkUpdates', 'post', $appData); - $update = json_decode($data); - - // Check if this version is up to date - if ($update->latest_version || version_compare($version, $update->version, 'ge')) { - return [false, 'Up to date!']; - } - - // Fetch the package - try { - download_package($update); - } catch (\Exception $e) { - return [ - false, - "Could not automatically download the package. Please download {$update->package}, place it in the same directory as this upgrade script, and try again. ". - "When moving the file, name it `{$update->version}-update.zip`", - ]; - } - - return [true, $update->version]; - } catch (\Exception $exception) { - return [false, $exception->getMessage()]; - } -} - -/** - * @param object $update - * - * @throws Exception - * - * @return bool - */ -function download_package($update) -{ - $packageName = $update->version.'-update.zip'; - $target = __DIR__.'/'.$packageName; - - if (file_exists($target)) { - return true; - } - - $data = make_request($update->package); - - if (!file_put_contents($target, $data)) { - throw new \Exception(); - } -} - -/** - * @return int - */ -function extract_package($version) -{ - $zipFile = __DIR__.'/'.$version.'-update.zip'; - - if (!file_exists($zipFile)) { - return [false, 'Package could not be found!']; - } - - $zipper = new \ZipArchive(); - $archive = $zipper->open($zipFile); - - if (true !== $archive) { - return [false, 'Could not open or read update package.']; - } - - if (!$zipper->extractTo(MAUTIC_UPGRADE_ROOT)) { - return [false, 'Could not extract update package']; - } - - $zipper->close(); - - return [true, 'success']; -} - /** * Clears the application cache. * @@ -513,59 +271,6 @@ function run_symfony_command($command, array $args) return 0 === $exitCode; } -/** - * Build the cache. - * - * @return array - */ -function build_cache() -{ - // Rebuild the cache - return run_symfony_command('cache:clear', ['--no-interaction', '--env=prod', '--no-debug', '--no-warmup']); -} - -/** - * Apply critical migrations. - */ -function apply_critical_migrations() -{ - $criticalMigrations = json_decode(file_get_contents(__DIR__.'/critical_migrations.txt'), true); - - $success = true; - - $minExecutionTime = 300; - $maxExecutionTime = (int) ini_get('max_execution_time'); - if ($maxExecutionTime > 0 && $maxExecutionTime < $minExecutionTime) { - ini_set('max_execution_time', $minExecutionTime); - } - - if ($criticalMigrations) { - foreach ($criticalMigrations as $version) { - if (!run_symfony_command('doctrine:migrations:migrate', ['--no-interaction', '--env=prod', '--no-debug', $version])) { - $success = false; - } - } - } - - return $success; -} - -/** - * Apply all migrations. - * - * @return bool - */ -function apply_migrations() -{ - $minExecutionTime = 300; - $maxExecutionTime = (int) ini_get('max_execution_time'); - if ($maxExecutionTime > 0 && $maxExecutionTime < $minExecutionTime) { - ini_set('max_execution_time', $minExecutionTime); - } - - return run_symfony_command('doctrine:migrations:migrate', ['--no-interaction', '--env=prod', '--no-debug']); -} - /** * Copy a folder. * diff --git a/app/bundles/ApiBundle/ApiEvents.php b/app/bundles/ApiBundle/ApiEvents.php index 65b828f7d2d..83eabcb009b 100644 --- a/app/bundles/ApiBundle/ApiEvents.php +++ b/app/bundles/ApiBundle/ApiEvents.php @@ -1,14 +1,5 @@ [ 'public' => [ diff --git a/app/bundles/ApiBundle/Controller/ClientController.php b/app/bundles/ApiBundle/Controller/ClientController.php index 959c35d7eb2..0504b11d680 100644 --- a/app/bundles/ApiBundle/Controller/ClientController.php +++ b/app/bundles/ApiBundle/Controller/ClientController.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - * - * @copyright 2014 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link http://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\ApiBundle\Form\Validator\Constraints; use Symfony\Component\Form\Exception\UnexpectedTypeException; diff --git a/app/bundles/ApiBundle/Helper/BatchIdToEntityHelper.php b/app/bundles/ApiBundle/Helper/BatchIdToEntityHelper.php index dc0ec98db34..96e68e22eb2 100644 --- a/app/bundles/ApiBundle/Helper/BatchIdToEntityHelper.php +++ b/app/bundles/ApiBundle/Helper/BatchIdToEntityHelper.php @@ -1,14 +1,5 @@ getBatchEntities($parameters, $errors, false, 'id', $model); diff --git a/app/bundles/ApiBundle/Tests/EntityResultHelperTest.php b/app/bundles/ApiBundle/Tests/EntityResultHelperTest.php index 877225f1031..c5d3b65c212 100644 --- a/app/bundles/ApiBundle/Tests/EntityResultHelperTest.php +++ b/app/bundles/ApiBundle/Tests/EntityResultHelperTest.php @@ -1,14 +1,5 @@ [ 'main' => [ diff --git a/app/bundles/AssetBundle/Controller/AjaxController.php b/app/bundles/AssetBundle/Controller/AjaxController.php index 6dd2da6ab92..85a0d6fe42e 100644 --- a/app/bundles/AssetBundle/Controller/AjaxController.php +++ b/app/bundles/AssetBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ accessDenied(); } - if ('POST' == $this->request->getMethod()) { - $this->setListFilters(); - } + $this->setListFilters(); //set limits $limit = $this->get('session')->get('mautic.asset.limit', $this->get('mautic.helper.core_parameters')->get('default_assetlimit')); @@ -66,8 +55,8 @@ public function indexAction($page = 1) ['column' => 'a.createdBy', 'expr' => 'eq', 'value' => $this->user->getId()]; } - $orderBy = $this->get('session')->get('mautic.asset.orderby', 'a.title'); - $orderByDir = $this->get('session')->get('mautic.asset.orderbydir', 'DESC'); + $orderBy = $this->get('session')->get('mautic.asset.orderby', 'a.dateModified'); + $orderByDir = $this->get('session')->get('mautic.asset.orderbydir', $this->getDefaultOrderDirection()); $assets = $model->getEntities( [ @@ -333,7 +322,7 @@ public function newAction($entity = null) $entity->setUploadDir($this->get('mautic.helper.core_parameters')->get('upload_dir')); $entity->preUpload(); $entity->upload(); - + $entity->setDateModified(new \DateTime()); //form is valid so process the data $model->saveEntity($entity); @@ -772,4 +761,14 @@ public function remoteAction() ], ]); } + + public function getModelName(): string + { + return 'asset'; + } + + protected function getDefaultOrderDirection(): string + { + return 'DESC'; + } } diff --git a/app/bundles/AssetBundle/Controller/PublicController.php b/app/bundles/AssetBundle/Controller/PublicController.php index 1021ad5f1d2..35d62eb5b2a 100644 --- a/app/bundles/AssetBundle/Controller/PublicController.php +++ b/app/bundles/AssetBundle/Controller/PublicController.php @@ -1,14 +1,5 @@ andWhere($q->expr()->like('a.title', ':search')) - ->setParameter('search', "{$search}%"); + ->setParameter('search', "%{$search}%"); } if (!$viewOther) { diff --git a/app/bundles/AssetBundle/Entity/Download.php b/app/bundles/AssetBundle/Entity/Download.php index 1aae84aceb9..b6cb636f4bb 100644 --- a/app/bundles/AssetBundle/Entity/Download.php +++ b/app/bundles/AssetBundle/Entity/Download.php @@ -1,14 +1,5 @@ hasGroupBy()) { - $queryBuilder->groupBy('ad.asset_id'); + $queryBuilder->groupBy('ad.id'); } } diff --git a/app/bundles/AssetBundle/EventListener/SearchSubscriber.php b/app/bundles/AssetBundle/EventListener/SearchSubscriber.php index a98a60484ac..7e0b1873e5c 100644 --- a/app/bundles/AssetBundle/EventListener/SearchSubscriber.php +++ b/app/bundles/AssetBundle/EventListener/SearchSubscriber.php @@ -1,14 +1,5 @@ security->isGranted('asset:assets:viewother'); + $request = $this->requestStack->getCurrentRequest(); $repo = $this->getRepository(); $repo->setCurrentUser($this->userHelper->getUser()); + // During the form submit & edit, make sure that the data is checked against available assets + if ('mautic_segment_action' === $request->get('_route') && + (Request::METHOD_POST === $request->getMethod() || 'edit' === $request->get('objectAction')) + ) { + $limit = 0; + } $results = $repo->getAssetList($filter, $limit, 0, $viewOther); break; case 'category': diff --git a/app/bundles/AssetBundle/Security/Permissions/AssetPermissions.php b/app/bundles/AssetBundle/Security/Permissions/AssetPermissions.php index 9fce0aa288b..fe7f7392376 100644 --- a/app/bundles/AssetBundle/Security/Permissions/AssetPermissions.php +++ b/app/bundles/AssetBundle/Security/Permissions/AssetPermissions.php @@ -1,14 +1,5 @@ setTitle('test'); + $asset->setAlias('test'); + $asset->setDateAdded(new \DateTime('2020-02-07 20:29:02')); + $asset->setDateModified(new \DateTime('2020-03-21 20:29:02')); + $asset->setCreatedByUser('Test User'); + + $this->em->persist($asset); + $this->em->flush(); + $this->em->clear(); + + $urlAlias = 'assets'; + $routeAlias = 'asset'; + $column = 'dateModified'; + $column2 = 'title'; + $tableAlias = 'a.'; + + $this->getControllerColumnTests($urlAlias, $routeAlias, $column, $tableAlias, $column2); + } +} diff --git a/app/bundles/AssetBundle/Tests/Controller/AssetDetailFunctionalTest.php b/app/bundles/AssetBundle/Tests/Controller/AssetDetailFunctionalTest.php index 2847797cfc6..00b088dbc19 100644 --- a/app/bundles/AssetBundle/Tests/Controller/AssetDetailFunctionalTest.php +++ b/app/bundles/AssetBundle/Tests/Controller/AssetDetailFunctionalTest.php @@ -1,15 +1,5 @@ queryBuilder->expects($this->once()) ->method('groupBy') - ->with('ad.asset_id'); + ->with('ad.id'); $this->assertFalse($event->hasGroupBy()); diff --git a/app/bundles/AssetBundle/Views/Asset/list.html.php b/app/bundles/AssetBundle/Views/Asset/list.html.php index 3620acf52d5..43046822613 100644 --- a/app/bundles/AssetBundle/Views/Asset/list.html.php +++ b/app/bundles/AssetBundle/Views/Asset/list.html.php @@ -38,7 +38,6 @@ 'orderBy' => 'a.title', 'text' => 'mautic.core.title', 'class' => 'col-asset-title', - 'default' => true, ] ); @@ -62,6 +61,37 @@ ] ); + echo $view->render( + 'MauticCoreBundle:Helper:tableheader.html.php', + [ + 'sessionVar' => 'asset', + 'orderBy' => 'a.dateAdded', + 'text' => 'mautic.lead.import.label.dateAdded', + 'class' => 'visible-md visible-lg col-asset-dateAdded', + ] + ); + + echo $view->render( + 'MauticCoreBundle:Helper:tableheader.html.php', + [ + 'sessionVar' => 'asset', + 'orderBy' => 'a.dateModified', + 'text' => 'mautic.lead.import.label.dateModified', + 'class' => 'visible-md visible-lg col-asset-dateModified', + 'default' => true, + ] + ); + + echo $view->render( + 'MauticCoreBundle:Helper:tableheader.html.php', + [ + 'sessionVar' => 'asset', + 'orderBy' => 'a.createdByUser', + 'text' => 'mautic.core.createdby', + 'class' => 'visible-md visible-lg col-asset-createdByUser', + ] + ); + echo $view->render( 'MauticCoreBundle:Helper:tableheader.html.php', [ @@ -148,6 +178,13 @@ getDownloadCount(); ?> + + getDateAdded() ? $view['date']->toDate($item->getDateAdded()) : ''; ?> + + + getDateModified() ? $view['date']->toDate($item->getDateModified()) : ''; ?> + + getCreatedByUser(); ?> getId(); ?> diff --git a/app/bundles/AssetBundle/Views/Public/download.html.php b/app/bundles/AssetBundle/Views/Public/download.html.php index d80c1c61ed7..b3d9bbc7f37 100644 --- a/app/bundles/AssetBundle/Views/Public/download.html.php +++ b/app/bundles/AssetBundle/Views/Public/download.html.php @@ -1,8 +1 @@ [ 'main' => [], diff --git a/app/bundles/CacheBundle/EventListener/CacheClearSubscriber.php b/app/bundles/CacheBundle/EventListener/CacheClearSubscriber.php index 0226cc2abb3..63575acea6f 100644 --- a/app/bundles/CacheBundle/EventListener/CacheClearSubscriber.php +++ b/app/bundles/CacheBundle/EventListener/CacheClearSubscriber.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2020 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CacheBundle\EventListener; use Mautic\CacheBundle\Cache\CacheProvider; diff --git a/app/bundles/CacheBundle/Exceptions/InvalidArgumentException.php b/app/bundles/CacheBundle/Exceptions/InvalidArgumentException.php index 8f8a0cece4e..1a674a069f7 100644 --- a/app/bundles/CacheBundle/Exceptions/InvalidArgumentException.php +++ b/app/bundles/CacheBundle/Exceptions/InvalidArgumentException.php @@ -2,14 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2018 Mautic. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CacheBundle\Exceptions; class InvalidArgumentException extends \Symfony\Component\Cache\Exception\InvalidArgumentException diff --git a/app/bundles/CacheBundle/MauticCacheBundle.php b/app/bundles/CacheBundle/MauticCacheBundle.php index 4bdf782a980..2ef6fb5481c 100644 --- a/app/bundles/CacheBundle/MauticCacheBundle.php +++ b/app/bundles/CacheBundle/MauticCacheBundle.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2020 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CacheBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; diff --git a/app/bundles/CacheBundle/Tests/Cache/CacheProviderTest.php b/app/bundles/CacheBundle/Tests/Cache/CacheProviderTest.php index 80a34e2497b..9cc471875d8 100644 --- a/app/bundles/CacheBundle/Tests/Cache/CacheProviderTest.php +++ b/app/bundles/CacheBundle/Tests/Cache/CacheProviderTest.php @@ -2,14 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2018 Mautic. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CacheBundle\Tests\Cache; use Mautic\CacheBundle\Cache\Adapter\FilesystemTagAwareAdapter; diff --git a/app/bundles/CacheBundle/Tests/EventListener/CacheClearSubscriberTest.php b/app/bundles/CacheBundle/Tests/EventListener/CacheClearSubscriberTest.php index 6ebc31c2537..98486d0b201 100644 --- a/app/bundles/CacheBundle/Tests/EventListener/CacheClearSubscriberTest.php +++ b/app/bundles/CacheBundle/Tests/EventListener/CacheClearSubscriberTest.php @@ -2,14 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2018 Mautic. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CacheBundle\Tests\EventListener; use Mautic\CacheBundle\Cache\Adapter\FilesystemTagAwareAdapter; diff --git a/app/bundles/CalendarBundle/Assets/js/calendar.js b/app/bundles/CalendarBundle/Assets/js/calendar.js index a71b6bb5dc6..e5a187c3cde 100644 --- a/app/bundles/CalendarBundle/Assets/js/calendar.js +++ b/app/bundles/CalendarBundle/Assets/js/calendar.js @@ -24,7 +24,7 @@ Mautic.initializeCalendarModals = function (container) { Mautic.loadCalendarEvents = function (container) { mQuery('#calendar').fullCalendar({ events: mauticAjaxUrl + "?action=calendar:generateData", - lang: 'en', + lang: mauticLocale, eventLimit: true, eventLimitText: "more", eventRender: function(event, element) { diff --git a/app/bundles/CalendarBundle/CalendarEvents.php b/app/bundles/CalendarBundle/CalendarEvents.php index ebb6f564c71..ea1fd6b8d36 100644 --- a/app/bundles/CalendarBundle/CalendarEvents.php +++ b/app/bundles/CalendarBundle/CalendarEvents.php @@ -1,14 +1,5 @@ [ 'main' => [ diff --git a/app/bundles/CalendarBundle/Controller/AjaxController.php b/app/bundles/CalendarBundle/Controller/AjaxController.php index 9ef9096b657..c0a9af796af 100644 --- a/app/bundles/CalendarBundle/Controller/AjaxController.php +++ b/app/bundles/CalendarBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ [ 'main' => [ @@ -262,7 +253,7 @@ ], ], 'mautic.campaign.model.event_log' => [ - 'class' => 'Mautic\CampaignBundle\Model\EventLogModel', + 'class' => \Mautic\CampaignBundle\Model\EventLogModel::class, 'arguments' => [ 'mautic.campaign.model.event', 'mautic.campaign.model.campaign', diff --git a/app/bundles/CampaignBundle/Controller/AjaxController.php b/app/bundles/CampaignBundle/Controller/AjaxController.php index adb3f58f166..45267c0b7ae 100644 --- a/app/bundles/CampaignBundle/Controller/AjaxController.php +++ b/app/bundles/CampaignBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ getContactMembership($contact); if (0 === count($membership)) { - return $this->returnError('mautic.campaign.error.contact_not_in_campaign', Response::HTTP_CONFLICT); + return $this->returnError( + $this->translator->trans( + 'mautic.campaign.error.contact_not_in_campaign', + ['%campaign%' => $campaignId, '%contact%' => $contactId] + ), + Response::HTTP_CONFLICT + ); } $this->campaign = $campaign; diff --git a/app/bundles/CampaignBundle/Controller/CampaignController.php b/app/bundles/CampaignBundle/Controller/CampaignController.php index 0c6b8767198..1a6ef3e84b9 100644 --- a/app/bundles/CampaignBundle/Controller/CampaignController.php +++ b/app/bundles/CampaignBundle/Controller/CampaignController.php @@ -1,14 +1,5 @@ get('mautic.campaign.orderby', 'c.dateModified'); - $orderByDir = $session->get('mautic.campaign.orderbydir', 'DESC'); + $orderByDir = $session->get('mautic.campaign.orderbydir', $this->getDefaultOrderDirection()); - list($count, $items) = $this->getIndexItems($start, $limit, $filter, $orderBy, $orderByDir); + [$count, $items] = $this->getIndexItems($start, $limit, $filter, $orderBy, $orderByDir); if ($count && $count < ($start + 1)) { //the number of entities are now less then the current page so redirect to the last page @@ -551,10 +542,10 @@ protected function beforeFormProcessed($entity, Form $form, $action, $isPost, $o { $sessionId = $this->getCampaignSessionId($entity, $action, $objectId); //set added/updated events - list($this->modifiedEvents, $this->deletedEvents, $this->campaignEvents) = $this->getSessionEvents($sessionId); + [$this->modifiedEvents, $this->deletedEvents, $this->campaignEvents] = $this->getSessionEvents($sessionId); //set added/updated sources - list($this->addedSources, $this->deletedSources, $campaignSources) = $this->getSessionSources($sessionId, $isClone); + [$this->addedSources, $this->deletedSources, $campaignSources] = $this->getSessionSources($sessionId, $isClone); $this->connections = $this->getSessionCanvasSettings($sessionId); if ($isPost) { @@ -615,7 +606,7 @@ protected function beforeEntitySave($entity, Form $form, $action, $objectId = nu } if ($isClone) { - list($this->addedSources, $this->deletedSources, $campaignSources) = $this->getSessionSources($objectId, $isClone); + [$this->addedSources, $this->deletedSources, $campaignSources] = $this->getSessionSources($objectId, $isClone); $this->getCampaignModel()->setLeadSources($entity, $campaignSources, []); // If this is a clone, we need to save the entity first to properly build the events, sources and canvas settings $this->getCampaignModel()->getRepository()->saveEntity($entity); @@ -745,7 +736,7 @@ protected function getIndexItems($start, $limit, $filter, $orderBy, $orderByDir, if ($updatedFilters) { foreach ($updatedFilters as $updatedFilter) { - list($clmn, $fltr) = explode(':', $updatedFilter); + [$clmn, $fltr] = explode(':', $updatedFilter); $newFilters[$clmn][] = $fltr; } @@ -1204,4 +1195,9 @@ private function getCampaignLogCountsProcessed(array &$campaignLogCounts): array return $campaignLogCountsProcessed; } + + protected function getDefaultOrderDirection(): string + { + return 'DESC'; + } } diff --git a/app/bundles/CampaignBundle/Controller/EventController.php b/app/bundles/CampaignBundle/Controller/EventController.php index 01003c7d7c7..338ab6084ee 100644 --- a/app/bundles/CampaignBundle/Controller/EventController.php +++ b/app/bundles/CampaignBundle/Controller/EventController.php @@ -1,14 +1,5 @@ |object[]|mixed[] */ public function getEntities(array $args = []) { diff --git a/app/bundles/CampaignBundle/Entity/FailedLeadEventLog.php b/app/bundles/CampaignBundle/Entity/FailedLeadEventLog.php index 9ffd9fac4fb..4515d6b2900 100644 --- a/app/bundles/CampaignBundle/Entity/FailedLeadEventLog.php +++ b/app/bundles/CampaignBundle/Entity/FailedLeadEventLog.php @@ -1,14 +1,5 @@ setGroupPrefix('campaignEventLog') - ->addProperties( - [ - 'ipAddress', - 'dateTriggered', - 'isScheduled', - 'triggerDate', - 'metadata', - 'nonActionPathTaken', - 'channel', - 'channelId', - 'rotation', - ] - ) - - // Add standalone groups - ->setGroupPrefix('campaignEventStandaloneLog') - ->addProperties( - [ - 'event', - 'lead', - 'campaign', - 'ipAddress', - 'dateTriggered', - 'isScheduled', - 'triggerDate', - 'metadata', - 'nonActionPathTaken', - 'channel', - 'channelId', - 'rotation', - ] - ) - ->build(); + ->addProperties( + [ + 'ipAddress', + 'dateTriggered', + 'isScheduled', + 'triggerDate', + 'metadata', + 'nonActionPathTaken', + 'channel', + 'channelId', + 'rotation', + ] + ) + + // Add standalone groups + ->setGroupPrefix('campaignEventStandaloneLog') + ->addProperties( + [ + 'event', + 'lead', + 'campaign', + 'ipAddress', + 'dateTriggered', + 'isScheduled', + 'triggerDate', + 'metadata', + 'nonActionPathTaken', + 'channel', + 'channelId', + 'rotation', + ] + ) + ->build(); } /** @@ -225,7 +217,7 @@ public function getId() } /** - * @return \DateTime + * @return \DateTime|null */ public function getDateTriggered() { @@ -246,7 +238,7 @@ public function setDateTriggered(\DateTime $dateTriggered = null) } /** - * @return IpAddress + * @return IpAddress|null */ public function getIpAddress() { @@ -264,7 +256,7 @@ public function setIpAddress(IpAddress $ipAddress) } /** - * @return LeadEntity + * @return LeadEntity|null */ public function getLead() { @@ -282,7 +274,7 @@ public function setLead(LeadEntity $lead) } /** - * @return Event + * @return Event|null */ public function getEvent() { @@ -314,7 +306,7 @@ public function getIsScheduled() } /** - * @param $isScheduled + * @param bool $isScheduled * * @return $this */ @@ -332,7 +324,7 @@ public function setIsScheduled($isScheduled) /** * If isScheduled was changed, this will have the previous state. * - * @return mixed + * @return bool|null */ public function getPreviousScheduledState() { @@ -340,7 +332,7 @@ public function getPreviousScheduledState() } /** - * @return mixed + * @return \DateTime|null */ public function getTriggerDate() { @@ -348,8 +340,6 @@ public function getTriggerDate() } /** - * @param \DateTime $triggerDate - * * @return $this */ public function setTriggerDate(\DateTime $triggerDate = null) @@ -361,7 +351,7 @@ public function setTriggerDate(\DateTime $triggerDate = null) } /** - * @return Campaign + * @return Campaign|null */ public function getCampaign() { @@ -387,7 +377,7 @@ public function getSystemTriggered() } /** - * @param $systemTriggered + * @param bool $systemTriggered * * @return $this */ @@ -399,7 +389,7 @@ public function setSystemTriggered($systemTriggered) } /** - * @return mixed + * @return bool */ public function getNonActionPathTaken() { @@ -407,7 +397,7 @@ public function getNonActionPathTaken() } /** - * @param $nonActionPathTaken + * @param bool $nonActionPathTaken * * @return $this */ @@ -419,7 +409,7 @@ public function setNonActionPathTaken($nonActionPathTaken) } /** - * @return mixed + * @return mixed[]|null */ public function getMetadata() { @@ -427,7 +417,7 @@ public function getMetadata() } /** - * @param $metadata + * @param mixed[] $metadata */ public function appendToMetadata($metadata) { @@ -440,7 +430,7 @@ public function appendToMetadata($metadata) } /** - * @param $metadata + * @param mixed[] $metadata * * @return $this */ @@ -457,7 +447,7 @@ public function setMetadata($metadata) } /** - * @return string + * @return string|null */ public function getChannel() { @@ -467,7 +457,7 @@ public function getChannel() /** * @param string $channel * - * @return LeadEventLog + * @return LeadEventLog|$this */ public function setChannel($channel) { @@ -477,7 +467,7 @@ public function setChannel($channel) } /** - * @return mixed + * @return int|null */ public function getChannelId() { @@ -485,7 +475,7 @@ public function getChannelId() } /** - * @param mixed $channelId + * @param int|null $channelId * * @return LeadEventLog */ @@ -497,7 +487,7 @@ public function setChannelId($channelId) } /** - * @return int + * @return int|null */ public function getRotation() { @@ -517,7 +507,7 @@ public function setRotation($rotation) } /** - * @return FailedLeadEventLog + * @return FailedLeadEventLog|null */ public function getFailedLog() { @@ -525,9 +515,7 @@ public function getFailedLog() } /** - * @param FailedLeadEventLog $log - * - * return $this + * @return $this */ public function setFailedLog(FailedLeadEventLog $log = null) { diff --git a/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php b/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php index b641fbd5f29..eea45e77a90 100644 --- a/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php +++ b/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php @@ -1,14 +1,5 @@ $contacts + * @param bool $isInactiveEvent + * + * @return void * * @throws Dispatcher\Exception\LogNotProcessedException * @throws Dispatcher\Exception\LogPassedAndFailedException diff --git a/app/bundles/CampaignBundle/Executioner/Exception/CampaignNotExecutableException.php b/app/bundles/CampaignBundle/Executioner/Exception/CampaignNotExecutableException.php index 6a8c05c4320..23955442bdf 100644 --- a/app/bundles/CampaignBundle/Executioner/Exception/CampaignNotExecutableException.php +++ b/app/bundles/CampaignBundle/Executioner/Exception/CampaignNotExecutableException.php @@ -1,14 +1,5 @@ output) { @@ -165,7 +151,11 @@ private function addNewlyQualifiedMembers($totalContactsProcessed) } // Get next batch - $contacts = $this->campaignMemberRepository->getCampaignContactsBySegments($this->campaign->getId(), $this->contactLimiter); + $contacts = $this->campaignMemberRepository->getCampaignContactsBySegments( + $this->campaign->getId(), + $this->contactLimiter, + $this->campaign->allowRestart() + ); } $this->finishProgressBar(); @@ -174,15 +164,10 @@ private function addNewlyQualifiedMembers($totalContactsProcessed) } /** - * @param $totalContactsProcessed - * - * @return int - * * @throws RunLimitReachedException */ - private function removeUnqualifiedMembers($totalContactsProcessed) + private function removeUnqualifiedMembers(int $totalContactsProcessed): int { - $progress = null; $contactsProcessed = 0; if ($this->output) { @@ -234,10 +219,7 @@ private function removeUnqualifiedMembers($totalContactsProcessed) return $contactsProcessed; } - /** - * @param $total - */ - private function startProgressBar($total) + private function startProgressBar(int $total): void { if (!$this->output) { $this->progressBar = null; @@ -253,7 +235,7 @@ private function startProgressBar($total) $this->manager->setProgressBar($this->progressBar); } - private function finishProgressBar() + private function finishProgressBar(): void { if ($this->progressBar) { $this->progressBar->finish(); diff --git a/app/bundles/CampaignBundle/Membership/MembershipManager.php b/app/bundles/CampaignBundle/Membership/MembershipManager.php index d8dee1266df..8d18230c76d 100644 --- a/app/bundles/CampaignBundle/Membership/MembershipManager.php +++ b/app/bundles/CampaignBundle/Membership/MembershipManager.php @@ -1,14 +1,5 @@ eventModel = $eventModel; $this->campaignModel = $campaignModel; $this->ipLookupHelper = $ipLookupHelper; @@ -112,13 +89,21 @@ public function updateContactEvent(Event $event, Lead $contact, array $parameter // Check that contact is part of the campaign $membership = $campaign->getContactMembership($contact); if (0 === count($membership)) { - return 'mautic.campaign.error.contact_not_in_campaign'; + return $this->translator->trans( + 'mautic.campaign.error.contact_not_in_campaign', + ['%campaign%' => $campaign->getId(), '%contact%' => $contact->getId()], + 'flashes' + ); } /** @var \Mautic\CampaignBundle\Entity\Lead $m */ foreach ($membership as $m) { if ($m->getManuallyRemoved()) { - return 'mautic.campaign.error.contact_not_in_campaign'; + return $this->translator->trans( + 'mautic.campaign.error.contact_not_in_campaign', + ['%campaign%' => $campaign->getId(), '%contact%' => $contact->getId()], + 'flashes' + ); } } @@ -128,11 +113,28 @@ public function updateContactEvent(Event $event, Lead $contact, array $parameter if (count($logs)) { $log = $logs[0]; if ($log->getDateTriggered()) { - return 'mautic.campaign.error.event_already_executed'; + return $this->translator->trans( + 'mautic.campaign.error.event_already_executed', + [ + '%campaign%' => $campaign->getId(), + '%event%' => $event->getId(), + '%contact%' => $contact->getId(), + '%dateTriggered%' => $log->getDateTriggered()->format(\DateTimeInterface::ATOM), + ], + 'flashes' + ); } } else { if (!isset($parameters['triggerDate']) && !isset($parameters['dateTriggered'])) { - return 'mautic.campaign.error.event_must_be_scheduled'; + return $this->translator->trans( + 'mautic.campaign.error.event_must_be_scheduled', + [ + '%campaign%' => $campaign->getId(), + '%event%' => $event->getId(), + '%contact%' => $contact->getId(), + ], + 'flashes' + ); } $log = (new LeadEventLog()) @@ -150,7 +152,15 @@ public function updateContactEvent(Event $event, Lead $contact, array $parameter break; case 'triggerDate': if (Event::TYPE_DECISION === $event->getEventType()) { - return 'mautic.campaign.error.decision_cannot_be_scheduled'; + return $this->translator->trans( + 'mautic.campaign.error.decision_cannot_be_scheduled', + [ + '%campaign%' => $campaign->getId(), + '%event%' => $event->getId(), + '%contact%' => $contact->getId(), + ], + 'flashes' + ); } $log->setTriggerDate( new \DateTime($value) diff --git a/app/bundles/CampaignBundle/Model/EventModel.php b/app/bundles/CampaignBundle/Model/EventModel.php index 300ee90d0f1..76675c01553 100644 --- a/app/bundles/CampaignBundle/Model/EventModel.php +++ b/app/bundles/CampaignBundle/Model/EventModel.php @@ -1,14 +1,5 @@ getDateTriggered()) { + // This shouldn't normally happen but it's possible to have a log without a date triggered + // as it is a nullable field and it can be created without date triggered for example via API. + continue; + } + $timestamp = $log->getDateTriggered()->getTimestamp(); $timestamp -= ($timestamp % 3600); $dateFrom = ($now)->setTimestamp($timestamp); diff --git a/app/bundles/CampaignBundle/Security/Permissions/CampaignPermissions.php b/app/bundles/CampaignBundle/Security/Permissions/CampaignPermissions.php index a14aed6c5ab..1eb550d274b 100644 --- a/app/bundles/CampaignBundle/Security/Permissions/CampaignPermissions.php +++ b/app/bundles/CampaignBundle/Security/Permissions/CampaignPermissions.php @@ -1,14 +1,5 @@ setEmail('johana@doe.nohama'); + + $contact2 = new Lead(); + $contact2->setEmail('johana@doe.mohana'); + + $contact3 = new Lead(); + $contact3->setEmail('johana@doe.monana'); + + $campaign = new Campaign(); + $campaign->setName('Test Campaign'); + + $campaignMember1 = new CampaignMember(); + $campaignMember1->setLead($contact1); + $campaignMember1->setCampaign($campaign); + $campaignMember1->setManuallyAdded(true); + $campaignMember1->setDateAdded(new \DateTime()); + + $campaignMember2 = new CampaignMember(); + $campaignMember2->setLead($contact2); + $campaignMember2->setCampaign($campaign); + $campaignMember2->setManuallyAdded(true); + $campaignMember2->setDateAdded(new \DateTime()); + + $event1 = new Event(); + $event1->setCampaign($campaign); + $event1->setType('lead.changepoints'); + $event1->setEventType('action'); + $event1->setName('Test Event 1'); + + $event2 = new Event(); + $event2->setCampaign($campaign); + $event2->setType('lead.changepoints'); + $event2->setEventType('action'); + $event2->setName('Test Event 2'); + + $event3 = new Event(); + $event3->setCampaign($campaign); + $event3->setType('asset.download'); + $event3->setEventType('decision'); + $event3->setName('Test Event 3'); + + $campaign->addEvent(0, $event1); + $campaign->addEvent(1, $event2); + $campaign->addEvent(1, $event3); + + $this->em->persist($contact1); + $this->em->persist($contact2); + $this->em->persist($contact3); + $this->em->persist($event1); + $this->em->persist($event2); + $this->em->persist($event3); + $this->em->persist($campaign); + $this->em->persist($campaignMember1); + $this->em->persist($campaignMember2); + $this->em->flush(); + $this->em->clear(); + + $payload = [ + // This will fail because it already has dateTriggered. + [ + 'contactId' => $contact1->getId(), + 'eventId' => $event1->getId(), + 'dateTriggered' => '2016-01-10 00:00:00', + ], + [ + 'contactId' => $contact2->getId(), + 'eventId' => $event1->getId(), + 'triggerDate' => '2017-01-10 00:00:00', + ], + [ + 'contactId' => $contact1->getId(), + 'eventId' => $event2->getId(), + 'triggerDate' => '2016-01-11 00:00:00', + ], + [ + 'contactId' => $contact2->getId(), + 'eventId' => $event2->getId(), + 'triggerDate' => '2016-01-11 00:00:00', + ], + // This will fail because this contact isn't a campaign member. + [ + 'contactId' => $contact3->getId(), + 'eventId' => $event2->getId(), + 'triggerDate' => '2017-01-10 00:00:00', + ], + // This will fail because decision cannot be scheduled. + [ + 'contactId' => $contact1->getId(), + 'eventId' => $event3->getId(), + 'triggerDate' => '2016-01-11 00:00:00', + ], + ]; + + $this->client->request('PUT', '/api/campaigns/events/batch/edit', $payload); + $clientResponse = $this->client->getResponse(); + $response = json_decode($clientResponse->getContent(), true); + + Assert::assertCount(1, $response['events'][$event1->getId()]['contactLog']); + Assert::assertCount(2, $response['events'][$event2->getId()]['contactLog']); + Assert::assertCount(0, $response['events'][$event3->getId()]['contactLog']); + + $errorMessages = array_map( + fn (array $error) => $error['message'], + $response['errors'] + ); + + Assert::assertContains("The event {$event1->getId()} in the campaign {$campaign->getId()} has already been executed at 2016-01-10T00:00:00+00:00 for the contact {$contact2->getId()}.", $errorMessages); + Assert::assertContains("The contact {$contact3->getId()} is not in the campaign {$campaign->getId()}.", $errorMessages); + Assert::assertContains("A decision type event cannot be scheduled. Event: {$event3->getId()}, campaign: {$campaign->getId()}, contact: {$contact1->getId()}.", $errorMessages); + } +} diff --git a/app/bundles/CampaignBundle/Tests/Command/ExecuteEventCommandTest.php b/app/bundles/CampaignBundle/Tests/Command/ExecuteEventCommandTest.php index 1775a8a49d2..6a92cecddde 100644 --- a/app/bundles/CampaignBundle/Tests/Command/ExecuteEventCommandTest.php +++ b/app/bundles/CampaignBundle/Tests/Command/ExecuteEventCommandTest.php @@ -1,14 +1,5 @@ setLead($contact); $eventRepository = new class($campaign) extends EventRepository { - private $campaign; + private Campaign $campaign; public function __construct(Campaign $campaign) { @@ -94,6 +85,9 @@ public function __construct() { } + /** + * @param mixed[] $parameters + */ public function trans($id, array $parameters = [], $domain = null, $locale = null) { Assert::assertSame('mautic.campaign.campaign.jump_to_event.target_not_exist', $id); @@ -156,7 +150,7 @@ public function getId() $leadLog->setLead($contact); $eventRepository = new class($campaign) extends EventRepository { - private $campaign; + private Campaign $campaign; public function __construct(Campaign $campaign) { @@ -223,6 +217,8 @@ public function incrementCampaignRotationForContacts(array $contactIds, $campaig { Assert::assertSame([789], $contactIds); Assert::assertSame(111, $campaignId); + + return true; } }; $subscriber = new CampaignActionJumpToEventSubscriber( diff --git a/app/bundles/CampaignBundle/Tests/EventListener/CampaignSubscriberTest.php b/app/bundles/CampaignBundle/Tests/EventListener/CampaignSubscriberTest.php index 1814323098d..c038c12f83c 100644 --- a/app/bundles/CampaignBundle/Tests/EventListener/CampaignSubscriberTest.php +++ b/app/bundles/CampaignBundle/Tests/EventListener/CampaignSubscriberTest.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2020 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CampaignBundle\Tests\EventListener; use Mautic\CampaignBundle\CampaignEvents; diff --git a/app/bundles/CampaignBundle/Tests/Executioner/ContactFinder/InactiveContactFinderTest.php b/app/bundles/CampaignBundle/Tests/Executioner/ContactFinder/InactiveContactFinderTest.php index c2e22d48793..7321fb33819 100644 --- a/app/bundles/CampaignBundle/Tests/Executioner/ContactFinder/InactiveContactFinderTest.php +++ b/app/bundles/CampaignBundle/Tests/Executioner/ContactFinder/InactiveContactFinderTest.php @@ -1,14 +1,5 @@ + */ + public ArrayCollection $rootEvents; + + /** + * @return ArrayCollection + */ + public function getRootEvents(): ArrayCollection { return $this->rootEvents; } diff --git a/app/bundles/CampaignBundle/Tests/Executioner/Logger/EventLoggerTest.php b/app/bundles/CampaignBundle/Tests/Executioner/Logger/EventLoggerTest.php index 850787143d6..65d1326ce9b 100644 --- a/app/bundles/CampaignBundle/Tests/Executioner/Logger/EventLoggerTest.php +++ b/app/bundles/CampaignBundle/Tests/Executioner/Logger/EventLoggerTest.php @@ -1,14 +1,5 @@ manager = $this->createMock(MembershipManager::class); - $this->campaignMemberRepository = $this->createMock(LeadRepository::class); - $this->leadRepository = $this->createMock(\Mautic\LeadBundle\Entity\LeadRepository::class); + $this->campaignMemberRepository = $this->createMock(CampaignMemberRepository::class); + $this->leadRepository = $this->createMock(LeadRepository::class); $this->translator = $this->createMock(TranslatorInterface::class); + $this->membershipBuilder = new MembershipBuilder( + $this->manager, + $this->campaignMemberRepository, + $this->leadRepository, + $this->translator + ); } - public function testContactCountIsSkippedWhenOutputIsNull() + public function testContactCountIsSkippedWhenOutputIsNull(): void { - $builder = $this->getBuilder(); - $campaign = new Campaign(); $contactLimiter = new ContactLimiter(100); @@ -71,13 +75,11 @@ public function testContactCountIsSkippedWhenOutputIsNull() ->method('getOrphanedContacts') ->willReturn([]); - $builder->build($campaign, $contactLimiter, 1000); + $this->membershipBuilder->build($campaign, $contactLimiter, 1000); } - public function testContactsAreNotRemovedIfRunLimitReachedWhileAdding() + public function testContactsAreNotRemovedIfRunLimitReachedWhileAdding(): void { - $builder = $this->getBuilder(); - $campaign = new Campaign(); $contactLimiter = new ContactLimiter(100); @@ -92,18 +94,28 @@ public function testContactsAreNotRemovedIfRunLimitReachedWhileAdding() $this->campaignMemberRepository->expects($this->never()) ->method('getOrphanedContacts'); - $builder->build($campaign, $contactLimiter, 2); + $this->membershipBuilder->build($campaign, $contactLimiter, 2); } - public function testWhileLoopBreaksWithNoMoreContacts() + public function testWhileLoopBreaksWithNoMoreContacts(): void { - $builder = $this->getBuilder(); + $campaign = new class() extends Campaign { + public function getId() + { + return 111; + } + }; - $campaign = new Campaign(); $contactLimiter = new ContactLimiter(1); $this->campaignMemberRepository->expects($this->exactly(4)) ->method('getCampaignContactsBySegments') + ->withConsecutive( + [111, $contactLimiter, false], + [111, $contactLimiter, false], + [111, $contactLimiter, false], + [111, $contactLimiter, false] + ) ->willReturnOnConsecutiveCalls([20], [21], [22], []); $this->manager->expects($this->exactly(3)) @@ -120,19 +132,46 @@ public function testWhileLoopBreaksWithNoMoreContacts() ->method('getContactCollection') ->willReturn(new ArrayCollection([new Lead()])); - $builder->build($campaign, $contactLimiter, 100); + $this->membershipBuilder->build($campaign, $contactLimiter, 100); } - /** - * @return MembershipBuilder - */ - private function getBuilder() + public function testWhileLoopBreaksWithNoMoreContactsForRepeatableCampaign(): void { - return new MembershipBuilder( - $this->manager, - $this->campaignMemberRepository, - $this->leadRepository, - $this->translator - ); + $campaign = new class() extends Campaign { + public function getId() + { + return 111; + } + }; + + $campaign->setAllowRestart(true); + + $contactLimiter = new ContactLimiter(1); + + $this->campaignMemberRepository->expects($this->exactly(4)) + ->method('getCampaignContactsBySegments') + ->withConsecutive( + [111, $contactLimiter, true], + [111, $contactLimiter, true], + [111, $contactLimiter, true], + [111, $contactLimiter, true] + ) + ->willReturnOnConsecutiveCalls([20], [21], [22], []); + + $this->manager->expects($this->exactly(3)) + ->method('addContacts'); + + $this->campaignMemberRepository->expects($this->exactly(4)) + ->method('getOrphanedContacts') + ->willReturnOnConsecutiveCalls([23], [24], [25], []); + + $this->manager->expects($this->exactly(3)) + ->method('removeContacts'); + + $this->leadRepository->expects($this->exactly(6)) + ->method('getContactCollection') + ->willReturn(new ArrayCollection([new Lead()])); + + $this->membershipBuilder->build($campaign, $contactLimiter, 100); } } diff --git a/app/bundles/CampaignBundle/Tests/Membership/MembershipManagerTest.php b/app/bundles/CampaignBundle/Tests/Membership/MembershipManagerTest.php index 455fcba43f0..3520e2e6e1a 100644 --- a/app/bundles/CampaignBundle/Tests/Membership/MembershipManagerTest.php +++ b/app/bundles/CampaignBundle/Tests/Membership/MembershipManagerTest.php @@ -1,14 +1,5 @@ getColor() : 'inherit'; ?> - getDateAdded() ? $view['date']->toFull($item->getDateAdded()) : ''; ?> - getDateModified() ? $view['date']->toFull($item->getDateModified()) : ''; ?> + + getDateAdded() ? $view['date']->toDate($item->getDateAdded()) : ''; ?> + + + getDateModified() ? $view['date']->toDate($item->getDateModified()) : ''; ?> + getCreatedByUser(); ?> getId(); ?> diff --git a/app/bundles/CampaignBundle/Views/FormTheme/Event/_campaignevent_properties_row.html.php b/app/bundles/CampaignBundle/Views/FormTheme/Event/_campaignevent_properties_row.html.php index 474e18f5c1f..fb657c259e8 100644 --- a/app/bundles/CampaignBundle/Views/FormTheme/Event/_campaignevent_properties_row.html.php +++ b/app/bundles/CampaignBundle/Views/FormTheme/Event/_campaignevent_properties_row.html.php @@ -1,12 +1,4 @@ \n"; echo $view['form']->row($form); diff --git a/app/bundles/CategoryBundle/CategoryEvents.php b/app/bundles/CategoryBundle/CategoryEvents.php index f65693a1b28..055bd033184 100644 --- a/app/bundles/CategoryBundle/CategoryEvents.php +++ b/app/bundles/CategoryBundle/CategoryEvents.php @@ -1,14 +1,5 @@ [ 'main' => [ diff --git a/app/bundles/CategoryBundle/Controller/AjaxController.php b/app/bundles/CategoryBundle/Controller/AjaxController.php index 3082d684e31..b3facf4cbee 100644 --- a/app/bundles/CategoryBundle/Controller/AjaxController.php +++ b/app/bundles/CategoryBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ [ 'main' => [ diff --git a/app/bundles/ChannelBundle/Controller/AjaxController.php b/app/bundles/ChannelBundle/Controller/AjaxController.php index 30961ed0fa8..ed5fbe5fbf4 100644 --- a/app/bundles/ChannelBundle/Controller/AjaxController.php +++ b/app/bundles/ChannelBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ [ 'main' => [ diff --git a/app/bundles/ConfigBundle/ConfigEvents.php b/app/bundles/ConfigBundle/ConfigEvents.php index c6aba129169..4bb5fa86a74 100644 --- a/app/bundles/ConfigBundle/ConfigEvents.php +++ b/app/bundles/ConfigBundle/ConfigEvents.php @@ -1,14 +1,5 @@ form(); Assert::assertEquals($page3, $form['config[coreconfig][404_page]']->getValue()); // re-create the Symfony client to make config changes applied - $this->setUpSymfony(); + $this->setUpSymfony($this->configParams); // Request not found url page3 page content should be rendered $crawler = $this->client->request(Request::METHOD_GET, '/s/config/editnotfoundurlblablabla'); diff --git a/app/bundles/ConfigBundle/Tests/Controller/SysinfoControllerTest.php b/app/bundles/ConfigBundle/Tests/Controller/SysinfoControllerTest.php index 485cfc628d4..bc2aa878238 100644 --- a/app/bundles/ConfigBundle/Tests/Controller/SysinfoControllerTest.php +++ b/app/bundles/ConfigBundle/Tests/Controller/SysinfoControllerTest.php @@ -5,11 +5,11 @@ namespace Mautic\ConfigBundle\Tests\Controller; use Mautic\ConfigBundle\Model\SysinfoModel; -use Mautic\CoreBundle\Test\AbstractMauticTestCase; +use Mautic\CoreBundle\Test\MauticMysqlTestCase; use PHPUnit\Framework\Assert; use Symfony\Component\HttpFoundation\Request; -class SysinfoControllerTest extends AbstractMauticTestCase +class SysinfoControllerTest extends MauticMysqlTestCase { public function testDbInfoIsShown(): void { diff --git a/app/bundles/ConfigBundle/Tests/Event/ConfigBuilderEventTest.php b/app/bundles/ConfigBundle/Tests/Event/ConfigBuilderEventTest.php index 6c932abe10c..116e89502d3 100644 --- a/app/bundles/ConfigBundle/Tests/Event/ConfigBuilderEventTest.php +++ b/app/bundles/ConfigBundle/Tests/Event/ConfigBuilderEventTest.php @@ -1,14 +1,5 @@ { + mQuery.ajax({ + showLoadingBar: true, + url: mauticAjaxUrl + `?action=marketplace:installPackage`, + type: 'POST', + data: JSON.stringify({ + vendor: vendorName, + package: packageName + }), + dataType: 'json', + success: successCallback, + error: errorCallback + }); + }, + + /** + * @param string vendorName The packagist vendor name + * @param string packageName The packagist package name to remove + * @param callback successCallback Callback to be executed on success (jQuery success object) + * @param callback errorCallback Callback to be executed on error (jQuery error object) + */ + removePackage: (vendorName, packageName, successCallback, errorCallback) => { + mQuery.ajax({ + showLoadingBar: true, + url: mauticAjaxUrl + `?action=marketplace:removePackage`, + type: 'POST', + data: JSON.stringify({ + vendor: vendorName, + package: packageName + }), + dataType: 'json', + success: successCallback, + error: errorCallback + }); + }, +} \ No newline at end of file diff --git a/app/bundles/CoreBundle/Assets/js/1a.content.js b/app/bundles/CoreBundle/Assets/js/1a.content.js index 49b5dbe51fe..79cd35d8a27 100644 --- a/app/bundles/CoreBundle/Assets/js/1a.content.js +++ b/app/bundles/CoreBundle/Assets/js/1a.content.js @@ -1043,12 +1043,15 @@ Mautic.destroyChosen = function(el) { /** * Activate a typeahead lookup - * - * @param field - * @param target - * @param options */ Mautic.activateFieldTypeahead = function (field, target, options, action) { + var fieldId = '#' + field; + var fieldEl = mQuery('#' + field); + + if (fieldEl.length && fieldEl.parent('.twitter-typeahead').length) { + return; // If the parent exist then the typeahead was already initialized. Abort. + } + if (options && typeof options === 'String') { var keys = values = []; @@ -1060,7 +1063,7 @@ Mautic.activateFieldTypeahead = function (field, target, options, action) { values = options[0].split('|'); } - var fieldTypeahead = Mautic.activateTypeahead('#' + field, { + var fieldTypeahead = Mautic.activateTypeahead(fieldId, { dataOptions: values, dataOptionKeys: keys, minLength: 0 @@ -1076,14 +1079,18 @@ Mautic.activateFieldTypeahead = function (field, target, options, action) { typeAheadOptions.limit = options.limit; } - var fieldTypeahead = Mautic.activateTypeahead('#' + field, typeAheadOptions); + if (('undefined' !== typeof options) && ('undefined' !== typeof options.noRrecordMessage)) { + typeAheadOptions.noRrecordMessage = options.noRrecordMessage; + } + + var fieldTypeahead = Mautic.activateTypeahead(fieldId, typeAheadOptions); } var callback = function (event, datum) { - if (mQuery("#" + field).length && datum["value"]) { - mQuery("#" + field).val(datum["value"]); + if (fieldEl.length && datum["value"]) { + fieldEl.val(datum["value"]); - var lookupCallback = mQuery('#' + field).data("lookup-callback"); + var lookupCallback = mQuery(fieldId).data('lookup-callback'); if (lookupCallback && typeof Mautic[lookupCallback] == 'function') { Mautic[lookupCallback](field, datum); } @@ -1667,7 +1674,19 @@ Mautic.activateTypeahead = function (el, options) { } } + var noRrecordMessage = (options.noRrecordMessage) ? options.noRrecordMessage : mQuery(el).data('no-record-message'); var theName = el.replace(/[^a-z0-9\s]/gi, '').replace(/[-\s]/g, '_'); + var dataset = { + name: theName, + displayKey: options.displayKey, + source: (typeof theBloodhound != 'undefined') ? theBloodhound.ttAdapter() : substringMatcher(lookupOptions, lookupKeys) + }; + + if (noRrecordMessage) { + dataset.templates = { + empty: "

" + noRrecordMessage + "

" + } + } var theTypeahead = mQuery(el).typeahead( { @@ -1676,11 +1695,7 @@ Mautic.activateTypeahead = function (el, options) { minLength: options.minLength, multiple: options.multiple }, - { - name: theName, - displayKey: options.displayKey, - source: (typeof theBloodhound != 'undefined') ? theBloodhound.ttAdapter() : substringMatcher(lookupOptions, lookupKeys) - } + dataset ).on('keypress', function (event) { if ((event.keyCode || event.which) == 13) { mQuery(el).typeahead('close'); diff --git a/app/bundles/CoreBundle/Assets/js/libraries/4fa.fullcalendar.lang-all.js b/app/bundles/CoreBundle/Assets/js/libraries/4fa.fullcalendar.lang-all.js new file mode 100644 index 00000000000..c6e94e4807f --- /dev/null +++ b/app/bundles/CoreBundle/Assets/js/libraries/4fa.fullcalendar.lang-all.js @@ -0,0 +1,3 @@ +(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(function(){(t.defineLocale||t.lang).call(t,"ar-ma",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:6,doy:12}}),e.fullCalendar.datepickerLang("ar-ma","ar",{closeText:"إغلاق",prevText:"<السابق",nextText:"التالي>",currentText:"اليوم",monthNames:["كانون الثاني","شباط","آذار","نيسان","مايو","حزيران","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesShort:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesMin:["ح","ن","ث","ر","خ","ج","س"],weekHeader:"أسبوع",dateFormat:"dd/mm/yy",firstDay:6,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ar-ma",{defaultButtonText:{month:"شهر",week:"أسبوع",day:"يوم",list:"أجندة"},allDayText:"اليوم كله",eventLimitText:"أخرى"})})(),function(){var a={1:"١",2:"٢",3:"٣",4:"٤",5:"٥",6:"٦",7:"٧",8:"٨",9:"٩",0:"٠"},n={"١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","٠":"0"};(t.defineLocale||t.lang).call(t,"ar-sa",{months:"يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},meridiem:function(e){return 12>e?"ص":"م"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},preparse:function(e){return e.replace(/[۰-۹]/g,function(e){return n[e]}).replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return a[e]}).replace(/,/g,"،")},week:{dow:6,doy:12}}),e.fullCalendar.datepickerLang("ar-sa","ar",{closeText:"إغلاق",prevText:"<السابق",nextText:"التالي>",currentText:"اليوم",monthNames:["كانون الثاني","شباط","آذار","نيسان","مايو","حزيران","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesShort:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesMin:["ح","ن","ث","ر","خ","ج","س"],weekHeader:"أسبوع",dateFormat:"dd/mm/yy",firstDay:6,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ar-sa",{defaultButtonText:{month:"شهر",week:"أسبوع",day:"يوم",list:"أجندة"},allDayText:"اليوم كله",eventLimitText:"أخرى"})}(),function(){var a={1:"١",2:"٢",3:"٣",4:"٤",5:"٥",6:"٦",7:"٧",8:"٨",9:"٩",0:"٠"},n={"١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","٠":"0"};(t.defineLocale||t.lang).call(t,"ar",{months:"يناير/ كانون الثاني_فبراير/ شباط_مارس/ آذار_أبريل/ نيسان_مايو/ أيار_يونيو/ حزيران_يوليو/ تموز_أغسطس/ آب_سبتمبر/ أيلول_أكتوبر/ تشرين الأول_نوفمبر/ تشرين الثاني_ديسمبر/ كانون الأول".split("_"),monthsShort:"يناير/ كانون الثاني_فبراير/ شباط_مارس/ آذار_أبريل/ نيسان_مايو/ أيار_يونيو/ حزيران_يوليو/ تموز_أغسطس/ آب_سبتمبر/ أيلول_أكتوبر/ تشرين الأول_نوفمبر/ تشرين الثاني_ديسمبر/ كانون الأول".split("_"),weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},meridiem:function(e){return 12>e?"ص":"م"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},preparse:function(e){return e.replace(/[۰-۹]/g,function(e){return n[e]}).replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return a[e]}).replace(/,/g,"،")},week:{dow:6,doy:12}}),e.fullCalendar.datepickerLang("ar","ar",{closeText:"إغلاق",prevText:"<السابق",nextText:"التالي>",currentText:"اليوم",monthNames:["كانون الثاني","شباط","آذار","نيسان","مايو","حزيران","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesShort:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesMin:["ح","ن","ث","ر","خ","ج","س"],weekHeader:"أسبوع",dateFormat:"dd/mm/yy",firstDay:6,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ar",{defaultButtonText:{month:"شهر",week:"أسبوع",day:"يوم",list:"أجندة"},allDayText:"اليوم كله",eventLimitText:"أخرى"})}(),function(){(t.defineLocale||t.lang).call(t,"bg",{months:"януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември".split("_"),monthsShort:"янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек".split("_"),weekdays:"неделя_понеделник_вторник_сряда_четвъртък_петък_събота".split("_"),weekdaysShort:"нед_пон_вто_сря_чет_пет_съб".split("_"),weekdaysMin:"нд_пн_вт_ср_чт_пт_сб".split("_"),longDateFormat:{LT:"H:mm",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Днес в] LT",nextDay:"[Утре в] LT",nextWeek:"dddd [в] LT",lastDay:"[Вчера в] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[В изминалата] dddd [в] LT";case 1:case 2:case 4:case 5:return"[В изминалия] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"след %s",past:"преди %s",s:"няколко секунди",m:"минута",mm:"%d минути",h:"час",hh:"%d часа",d:"ден",dd:"%d дни",M:"месец",MM:"%d месеца",y:"година",yy:"%d години"},ordinal:function(e){var t=e%10,a=e%100;return 0===e?e+"-ев":0===a?e+"-ен":a>10&&20>a?e+"-ти":1===t?e+"-ви":2===t?e+"-ри":7===t||8===t?e+"-ми":e+"-ти"},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("bg","bg",{closeText:"затвори",prevText:"<назад",nextText:"напред>",nextBigText:">>",currentText:"днес",monthNames:["Януари","Февруари","Март","Април","Май","Юни","Юли","Август","Септември","Октомври","Ноември","Декември"],monthNamesShort:["Яну","Фев","Мар","Апр","Май","Юни","Юли","Авг","Сеп","Окт","Нов","Дек"],dayNames:["Неделя","Понеделник","Вторник","Сряда","Четвъртък","Петък","Събота"],dayNamesShort:["Нед","Пон","Вто","Сря","Чет","Пет","Съб"],dayNamesMin:["Не","По","Вт","Ср","Че","Пе","Съ"],weekHeader:"Wk",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("bg",{defaultButtonText:{month:"Месец",week:"Седмица",day:"Ден",list:"График"},allDayText:"Цял ден",eventLimitText:function(e){return"+още "+e}})}(),function(){(t.defineLocale||t.lang).call(t,"ca",{months:"gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre".split("_"),monthsShort:"gen._febr._mar._abr._mai._jun._jul._ag._set._oct._nov._des.".split("_"),weekdays:"diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte".split("_"),weekdaysShort:"dg._dl._dt._dc._dj._dv._ds.".split("_"),weekdaysMin:"Dg_Dl_Dt_Dc_Dj_Dv_Ds".split("_"),longDateFormat:{LT:"H:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:function(){return"[avui a "+(1!==this.hours()?"les":"la")+"] LT"},nextDay:function(){return"[demà a "+(1!==this.hours()?"les":"la")+"] LT"},nextWeek:function(){return"dddd [a "+(1!==this.hours()?"les":"la")+"] LT"},lastDay:function(){return"[ahir a "+(1!==this.hours()?"les":"la")+"] LT"},lastWeek:function(){return"[el] dddd [passat a "+(1!==this.hours()?"les":"la")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"fa %s",s:"uns segons",m:"un minut",mm:"%d minuts",h:"una hora",hh:"%d hores",d:"un dia",dd:"%d dies",M:"un mes",MM:"%d mesos",y:"un any",yy:"%d anys"},ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("ca","ca",{closeText:"Tanca",prevText:"Anterior",nextText:"Següent",currentText:"Avui",monthNames:["gener","febrer","març","abril","maig","juny","juliol","agost","setembre","octubre","novembre","desembre"],monthNamesShort:["gen","feb","març","abr","maig","juny","jul","ag","set","oct","nov","des"],dayNames:["diumenge","dilluns","dimarts","dimecres","dijous","divendres","dissabte"],dayNamesShort:["dg","dl","dt","dc","dj","dv","ds"],dayNamesMin:["dg","dl","dt","dc","dj","dv","ds"],weekHeader:"Set",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ca",{defaultButtonText:{month:"Mes",week:"Setmana",day:"Dia",list:"Agenda"},allDayText:"Tot el dia",eventLimitText:"més"})}(),function(){function a(e){return e>1&&5>e&&1!==~~(e/10)}function n(e,t,n,r){var i=e+" ";switch(n){case"s":return t||r?"pár sekund":"pár sekundami";case"m":return t?"minuta":r?"minutu":"minutou";case"mm":return t||r?i+(a(e)?"minuty":"minut"):i+"minutami";case"h":return t?"hodina":r?"hodinu":"hodinou";case"hh":return t||r?i+(a(e)?"hodiny":"hodin"):i+"hodinami";case"d":return t||r?"den":"dnem";case"dd":return t||r?i+(a(e)?"dny":"dní"):i+"dny";case"M":return t||r?"měsíc":"měsícem";case"MM":return t||r?i+(a(e)?"měsíce":"měsíců"):i+"měsíci";case"y":return t||r?"rok":"rokem";case"yy":return t||r?i+(a(e)?"roky":"let"):i+"lety"}}var r="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),i="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");(t.defineLocale||t.lang).call(t,"cs",{months:r,monthsShort:i,monthsParse:function(e,t){var a,n=[];for(a=0;12>a;a++)n[a]=RegExp("^"+e[a]+"$|^"+t[a]+"$","i");return n}(r,i),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H.mm",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("cs","cs",{closeText:"Zavřít",prevText:"<Dříve",nextText:"Později>",currentText:"Nyní",monthNames:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthNamesShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],dayNames:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","čt","pá","so"],dayNamesMin:["ne","po","út","st","čt","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("cs",{defaultButtonText:{month:"Měsíc",week:"Týden",day:"Den",list:"Agenda"},allDayText:"Celý den",eventLimitText:function(e){return"+další: "+e}})}(),function(){(t.defineLocale||t.lang).call(t,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("da",{defaultButtonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})}(),function(){function a(e,t,a){var n={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return t?n[a][0]:n[a][1]}(t.defineLocale||t.lang).call(t,"de-at",{months:"Jänner_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jän._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm [Uhr]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT",sameElse:"L",nextDay:"[Morgen um] LT",nextWeek:"dddd [um] LT",lastDay:"[Gestern um] LT",lastWeek:"[letzten] dddd [um] LT"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:a,mm:"%d Minuten",h:a,hh:"%d Stunden",d:a,dd:a,M:a,MM:a,y:a,yy:a},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("de-at","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("de-at",{defaultButtonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(e){return"+ weitere "+e}})}(),function(){function a(e,t,a){var n={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[e+" Tage",e+" Tagen"],M:["ein Monat","einem Monat"],MM:[e+" Monate",e+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[e+" Jahre",e+" Jahren"]};return t?n[a][0]:n[a][1]}(t.defineLocale||t.lang).call(t,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm [Uhr]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT",sameElse:"L",nextDay:"[Morgen um] LT",nextWeek:"dddd [um] LT",lastDay:"[Gestern um] LT",lastWeek:"[letzten] dddd [um] LT"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:a,mm:"%d Minuten",h:a,hh:"%d Stunden",d:a,dd:a,M:a,MM:a,y:a,yy:a},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("de",{defaultButtonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(e){return"+ weitere "+e}})}(),function(){(t.defineLocale||t.lang).call(t,"el",{monthsNominativeEl:"Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"),monthsGenitiveEl:"Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"),months:function(e,t){return/D/.test(t.substring(0,t.indexOf("MMMM")))?this._monthsGenitiveEl[e.month()]:this._monthsNominativeEl[e.month()]},monthsShort:"Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"),weekdays:"Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"),weekdaysShort:"Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"),weekdaysMin:"Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"),meridiem:function(e,t,a){return e>11?a?"μμ":"ΜΜ":a?"πμ":"ΠΜ"},longDateFormat:{LT:"h:mm A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendarEl:{sameDay:"[Σήμερα {}] LT",nextDay:"[Αύριο {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[Χθες {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[το προηγούμενο] dddd [{}] LT";default:return"[την προηγούμενη] dddd [{}] LT"}},sameElse:"L"},calendar:function(e,t){var a=this._calendarEl[e],n=t&&t.hours();return"function"==typeof a&&(a=a.apply(t)),a.replace("{}",1===n%12?"στη":"στις")},relativeTime:{future:"σε %s",past:"%s πριν",s:"δευτερόλεπτα",m:"ένα λεπτό",mm:"%d λεπτά",h:"μία ώρα",hh:"%d ώρες",d:"μία μέρα",dd:"%d μέρες",M:"ένας μήνας",MM:"%d μήνες",y:"ένας χρόνος",yy:"%d χρόνια"},ordinal:function(e){return e+"η"},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("el","el",{closeText:"Κλείσιμο",prevText:"Προηγούμενος",nextText:"Επόμενος",currentText:"Τρέχων Μήνας",monthNames:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthNamesShort:["Ιαν","Φεβ","Μαρ","Απρ","Μαι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],dayNames:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],dayNamesShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],dayNamesMin:["Κυ","Δε","Τρ","Τε","Πε","Πα","Σα"],weekHeader:"Εβδ",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("el",{defaultButtonText:{month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Ατζέντα"},allDayText:"Ολοήμερο",eventLimitText:"περισσότερα"})}(),function(){(t.defineLocale||t.lang).call(t,"en-au",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinal:function(e){var t=e%10,a=1===~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+a},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("en-au","en-AU",{closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("en-au")}(),function(){(t.defineLocale||t.lang).call(t,"en-ca",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",L:"YYYY-MM-DD",LL:"D MMMM, YYYY",LLL:"D MMMM, YYYY LT",LLLL:"dddd, D MMMM, YYYY LT"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinal:function(e){var t=e%10,a=1===~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+a}}),e.fullCalendar.lang("en-ca")}(),function(){(t.defineLocale||t.lang).call(t,"en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinal:function(e){var t=e%10,a=1===~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th";return e+a},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("en-gb","en-GB",{closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("en-gb",{columnFormat:{week:"ddd D/M"}})}(),function(){var a="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),n="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(t.defineLocale||t.lang).call(t,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,t){return/-MMM-/.test(t)?n[e.month()]:a[e.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",L:"DD/MM/YYYY",LL:"D [de] MMMM [del] YYYY",LLL:"D [de] MMMM [del] YYYY LT",LLLL:"dddd, D [de] MMMM [del] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("es",{defaultButtonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo
el día",eventLimitText:"más"})}(),function(){var a={1:"۱",2:"۲",3:"۳",4:"۴",5:"۵",6:"۶",7:"۷",8:"۸",9:"۹",0:"۰"},n={"۱":"1","۲":"2","۳":"3","۴":"4","۵":"5","۶":"6","۷":"7","۸":"8","۹":"9","۰":"0"};(t.defineLocale||t.lang).call(t,"fa",{months:"ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر".split("_"),monthsShort:"ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر".split("_"),weekdays:"یک‌شنبه_دوشنبه_سه‌شنبه_چهارشنبه_پنج‌شنبه_جمعه_شنبه".split("_"),weekdaysShort:"یک‌شنبه_دوشنبه_سه‌شنبه_چهارشنبه_پنج‌شنبه_جمعه_شنبه".split("_"),weekdaysMin:"ی_د_س_چ_پ_ج_ش".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},meridiem:function(e){return 12>e?"قبل از ظهر":"بعد از ظهر"},calendar:{sameDay:"[امروز ساعت] LT",nextDay:"[فردا ساعت] LT",nextWeek:"dddd [ساعت] LT",lastDay:"[دیروز ساعت] LT",lastWeek:"dddd [پیش] [ساعت] LT",sameElse:"L"},relativeTime:{future:"در %s",past:"%s پیش",s:"چندین ثانیه",m:"یک دقیقه",mm:"%d دقیقه",h:"یک ساعت",hh:"%d ساعت",d:"یک روز",dd:"%d روز",M:"یک ماه",MM:"%d ماه",y:"یک سال",yy:"%d سال"},preparse:function(e){return e.replace(/[۰-۹]/g,function(e){return n[e]}).replace(/،/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return a[e]}).replace(/,/g,"،")},ordinal:"%dم",week:{dow:6,doy:12}}),e.fullCalendar.datepickerLang("fa","fa",{closeText:"بستن",prevText:"<قبلی",nextText:"بعدی>",currentText:"امروز",monthNames:["فروردين","ارديبهشت","خرداد","تير","مرداد","شهريور","مهر","آبان","آذر","دی","بهمن","اسفند"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["يکشنبه","دوشنبه","سه‌شنبه","چهارشنبه","پنجشنبه","جمعه","شنبه"],dayNamesShort:["ی","د","س","چ","پ","ج","ش"],dayNamesMin:["ی","د","س","چ","پ","ج","ش"],weekHeader:"هف",dateFormat:"yy/mm/dd",firstDay:6,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fa",{defaultButtonText:{month:"ماه",week:"هفته",day:"روز",list:"برنامه"},allDayText:"تمام روز",eventLimitText:function(e){return"بیش از "+e}})}(),function(){function a(e,t,a,r){var i="";switch(a){case"s":return r?"muutaman sekunnin":"muutama sekunti";case"m":return r?"minuutin":"minuutti";case"mm":i=r?"minuutin":"minuuttia";break;case"h":return r?"tunnin":"tunti";case"hh":i=r?"tunnin":"tuntia";break;case"d":return r?"päivän":"päivä";case"dd":i=r?"päivän":"päivää";break;case"M":return r?"kuukauden":"kuukausi";case"MM":i=r?"kuukauden":"kuukautta";break;case"y":return r?"vuoden":"vuosi";case"yy":i=r?"vuoden":"vuotta"}return i=n(e,r)+" "+i}function n(e,t){return 10>e?t?i[e]:r[e]:e}var r="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),i=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",r[7],r[8],r[9]];(t.defineLocale||t.lang).call(t,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:a,m:a,mm:a,h:a,hh:a,d:a,dd:a,M:a,MM:a,y:a,yy:a},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fi",{defaultButtonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})}(),function(){(t.defineLocale||t.lang).call(t,"fr-ca",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinal:function(e){return e+(1===e?"er":"") +}}),e.fullCalendar.datepickerLang("fr-ca","fr-CA",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"yy-mm-dd",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fr-ca",{defaultButtonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la
journée",eventLimitText:"en plus"})}(),function(){(t.defineLocale||t.lang).call(t,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinal:function(e){return e+(1===e?"er":"")},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avril","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("fr",{defaultButtonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la
journée",eventLimitText:"en plus"})}(),function(){var a={1:"१",2:"२",3:"३",4:"४",5:"५",6:"६",7:"७",8:"८",9:"९",0:"०"},n={"१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9","०":"0"};(t.defineLocale||t.lang).call(t,"hi",{months:"जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर".split("_"),monthsShort:"जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.".split("_"),weekdays:"रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार".split("_"),weekdaysShort:"रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि".split("_"),weekdaysMin:"र_सो_मं_बु_गु_शु_श".split("_"),longDateFormat:{LT:"A h:mm बजे",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},calendar:{sameDay:"[आज] LT",nextDay:"[कल] LT",nextWeek:"dddd, LT",lastDay:"[कल] LT",lastWeek:"[पिछले] dddd, LT",sameElse:"L"},relativeTime:{future:"%s में",past:"%s पहले",s:"कुछ ही क्षण",m:"एक मिनट",mm:"%d मिनट",h:"एक घंटा",hh:"%d घंटे",d:"एक दिन",dd:"%d दिन",M:"एक महीने",MM:"%d महीने",y:"एक वर्ष",yy:"%d वर्ष"},preparse:function(e){return e.replace(/[१२३४५६७८९०]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return a[e]})},meridiem:function(e){return 4>e?"रात":10>e?"सुबह":17>e?"दोपहर":20>e?"शाम":"रात"},week:{dow:0,doy:6}}),e.fullCalendar.datepickerLang("hi","hi",{closeText:"बंद",prevText:"पिछला",nextText:"अगला",currentText:"आज",monthNames:["जनवरी ","फरवरी","मार्च","अप्रेल","मई","जून","जूलाई","अगस्त ","सितम्बर","अक्टूबर","नवम्बर","दिसम्बर"],monthNamesShort:["जन","फर","मार्च","अप्रेल","मई","जून","जूलाई","अग","सित","अक्ट","नव","दि"],dayNames:["रविवार","सोमवार","मंगलवार","बुधवार","गुरुवार","शुक्रवार","शनिवार"],dayNamesShort:["रवि","सोम","मंगल","बुध","गुरु","शुक्र","शनि"],dayNamesMin:["रवि","सोम","मंगल","बुध","गुरु","शुक्र","शनि"],weekHeader:"हफ्ता",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("hi",{defaultButtonText:{month:"महीना",week:"सप्ताह",day:"दिन",list:"कार्यसूची"},allDayText:"सभी दिन",eventLimitText:function(e){return"+अधिक "+e}})}(),function(){function a(e,t,a){var n=e+" ";switch(a){case"m":return t?"jedna minuta":"jedne minute";case"mm":return n+=1===e?"minuta":2===e||3===e||4===e?"minute":"minuta";case"h":return t?"jedan sat":"jednog sata";case"hh":return n+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return n+=1===e?"dan":"dana";case"MM":return n+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return n+=1===e?"godina":2===e||3===e||4===e?"godine":"godina"}}(t.defineLocale||t.lang).call(t,"hr",{months:"sječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac".split("_"),monthsShort:"sje._vel._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.".split("_"),weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),longDateFormat:{LT:"H:mm",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",m:a,mm:a,h:a,hh:a,d:"dan",dd:a,M:"mjesec",MM:a,y:"godinu",yy:a},ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("hr","hr",{closeText:"Zatvori",prevText:"<",nextText:">",currentText:"Danas",monthNames:["Siječanj","Veljača","Ožujak","Travanj","Svibanj","Lipanj","Srpanj","Kolovoz","Rujan","Listopad","Studeni","Prosinac"],monthNamesShort:["Sij","Velj","Ožu","Tra","Svi","Lip","Srp","Kol","Ruj","Lis","Stu","Pro"],dayNames:["Nedjelja","Ponedjeljak","Utorak","Srijeda","Četvrtak","Petak","Subota"],dayNamesShort:["Ned","Pon","Uto","Sri","Čet","Pet","Sub"],dayNamesMin:["Ne","Po","Ut","Sr","Če","Pe","Su"],weekHeader:"Tje",dateFormat:"dd.mm.yy.",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("hr",{defaultButtonText:{month:"Mjesec",week:"Tjedan",day:"Dan",list:"Raspored"},allDayText:"Cijeli dan",eventLimitText:function(e){return"+ još "+e}})}(),function(){function a(e,t,a,n){var r=e;switch(a){case"s":return n||t?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(n||t?" perc":" perce");case"mm":return r+(n||t?" perc":" perce");case"h":return"egy"+(n||t?" óra":" órája");case"hh":return r+(n||t?" óra":" órája");case"d":return"egy"+(n||t?" nap":" napja");case"dd":return r+(n||t?" nap":" napja");case"M":return"egy"+(n||t?" hónap":" hónapja");case"MM":return r+(n||t?" hónap":" hónapja");case"y":return"egy"+(n||t?" év":" éve");case"yy":return r+(n||t?" év":" éve")}return""}function n(e){return(e?"":"[múlt] ")+"["+r[this.day()]+"] LT[-kor]"}var r="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(t.defineLocale||t.lang).call(t,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiem:function(e,t,a){return 12>e?a===!0?"de":"DE":a===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return n.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return n.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:a,m:a,mm:a,h:a,hh:a,d:a,dd:a,M:a,MM:a,y:a,yy:a},ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),e.fullCalendar.lang("hu",{defaultButtonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})}(),function(){(t.defineLocale||t.lang).call(t,"id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiem:function(e){return 11>e?"pagi":15>e?"siang":19>e?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("id","id",{closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("id",{defaultButtonText:{month:"Bulan",week:"Minggu",day:"Hari",list:"Agenda"},allDayHtml:"Sehari
penuh",eventLimitText:"lebih"})}(),function(){function a(e){return 11===e%100?!0:1===e%10?!1:!0}function n(e,t,n,r){var i=e+" ";switch(n){case"s":return t||r?"nokkrar sekúndur":"nokkrum sekúndum";case"m":return t?"mínúta":"mínútu";case"mm":return a(e)?i+(t||r?"mínútur":"mínútum"):t?i+"mínúta":i+"mínútu";case"hh":return a(e)?i+(t||r?"klukkustundir":"klukkustundum"):i+"klukkustund";case"d":return t?"dagur":r?"dag":"degi";case"dd":return a(e)?t?i+"dagar":i+(r?"daga":"dögum"):t?i+"dagur":i+(r?"dag":"degi");case"M":return t?"mánuður":r?"mánuð":"mánuði";case"MM":return a(e)?t?i+"mánuðir":i+(r?"mánuði":"mánuðum"):t?i+"mánuður":i+(r?"mánuð":"mánuði");case"y":return t||r?"ár":"ári";case"yy":return a(e)?i+(t||r?"ár":"árum"):i+(t||r?"ár":"ári")}}(t.defineLocale||t.lang).call(t,"is",{months:"janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember".split("_"),monthsShort:"jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des".split("_"),weekdays:"sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur".split("_"),weekdaysShort:"sun_mán_þri_mið_fim_fös_lau".split("_"),weekdaysMin:"Su_Má_Þr_Mi_Fi_Fö_La".split("_"),longDateFormat:{LT:"H:mm",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd, D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[í dag kl.] LT",nextDay:"[á morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[í gær kl.] LT",lastWeek:"[síðasta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s síðan",s:n,m:n,mm:n,h:"klukkustund",hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("is","is",{closeText:"Loka",prevText:"< Fyrri",nextText:"Næsti >",currentText:"Í dag",monthNames:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],dayNames:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],dayNamesShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],dayNamesMin:["Su","Má","Þr","Mi","Fi","Fö","La"],weekHeader:"Vika",dateFormat:"dd.mm.yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("is",{defaultButtonText:{month:"Mánuður",week:"Vika",day:"Dagur",list:"Dagskrá"},allDayHtml:"Allan
daginn",eventLimitText:"meira"})}(),function(){(t.defineLocale||t.lang).call(t,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:"[lo scorso] dddd [alle] LT",sameElse:"L"},relativeTime:{future:function(e){return(/^[0-9].+$/.test(e)?"tra":"in")+" "+e},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("it",{defaultButtonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il
giorno",eventLimitText:function(e){return"+altri "+e}})}(),function(){(t.defineLocale||t.lang).call(t,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiem:function(e){return 12>e?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),e.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),e.fullCalendar.lang("ja",{defaultButtonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(e){return"他 "+e+" 件"}})}(),function(){(t.defineLocale||t.lang).call(t,"ko",{months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),longDateFormat:{LT:"A h시 mm분",L:"YYYY.MM.DD",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 LT",LLLL:"YYYY년 MMMM D일 dddd LT"},meridiem:function(e){return 12>e?"오전":"오후"},calendar:{sameDay:"오늘 LT",nextDay:"내일 LT",nextWeek:"dddd LT",lastDay:"어제 LT",lastWeek:"지난주 dddd LT",sameElse:"L"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇초",ss:"%d초",m:"일분",mm:"%d분",h:"한시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한달",MM:"%d달",y:"일년",yy:"%d년"},ordinal:"%d일",meridiemParse:/(오전|오후)/,isPM:function(e){return"오후"===e}}),e.fullCalendar.datepickerLang("ko","ko",{closeText:"닫기",prevText:"이전달",nextText:"다음달",currentText:"오늘",monthNames:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],monthNamesShort:["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월","12월"],dayNames:["일요일","월요일","화요일","수요일","목요일","금요일","토요일"],dayNamesShort:["일","월","화","수","목","금","토"],dayNamesMin:["일","월","화","수","목","금","토"],weekHeader:"Wk",dateFormat:"yy-mm-dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"년"}),e.fullCalendar.lang("ko",{defaultButtonText:{month:"월",week:"주",day:"일",list:"일정목록"},allDayText:"종일",eventLimitText:"개"})}(),function(){function a(e,t,a,n){return t?"kelios sekundės":n?"kelių sekundžių":"kelias sekundes"}function n(e,t,a,n){return t?i(a)[0]:n?i(a)[1]:i(a)[2]}function r(e){return 0===e%10||e>10&&20>e}function i(e){return d[e].split("_")}function s(e,t,a,s){var o=e+" ";return 1===e?o+n(e,t,a[0],s):t?o+(r(e)?i(a)[1]:i(a)[0]):s?o+i(a)[1]:o+(r(e)?i(a)[1]:i(a)[2])}function o(e,t){var a=-1===t.indexOf("dddd HH:mm"),n=l[e.day()];return a?n:n.substring(0,n.length-2)+"į"}var d={m:"minutė_minutės_minutę",mm:"minutės_minučių_minutes",h:"valanda_valandos_valandą",hh:"valandos_valandų_valandas",d:"diena_dienos_dieną",dd:"dienos_dienų_dienas",M:"mėnuo_mėnesio_mėnesį",MM:"mėnesiai_mėnesių_mėnesius",y:"metai_metų_metus",yy:"metai_metų_metus"},l="sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis".split("_");(t.defineLocale||t.lang).call(t,"lt",{months:"sausio_vasario_kovo_balandžio_gegužės_birželio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio".split("_"),monthsShort:"sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd".split("_"),weekdays:o,weekdaysShort:"Sek_Pir_Ant_Tre_Ket_Pen_Šeš".split("_"),weekdaysMin:"S_P_A_T_K_Pn_Š".split("_"),longDateFormat:{LT:"HH:mm",L:"YYYY-MM-DD",LL:"YYYY [m.] MMMM D [d.]",LLL:"YYYY [m.] MMMM D [d.], LT [val.]",LLLL:"YYYY [m.] MMMM D [d.], dddd, LT [val.]",l:"YYYY-MM-DD",ll:"YYYY [m.] MMMM D [d.]",lll:"YYYY [m.] MMMM D [d.], LT [val.]",llll:"YYYY [m.] MMMM D [d.], ddd, LT [val.]"},calendar:{sameDay:"[Šiandien] LT",nextDay:"[Rytoj] LT",nextWeek:"dddd LT",lastDay:"[Vakar] LT",lastWeek:"[Praėjusį] dddd LT",sameElse:"L"},relativeTime:{future:"po %s",past:"prieš %s",s:a,m:n,mm:s,h:n,hh:s,d:n,dd:s,M:n,MM:s,y:n,yy:s},ordinal:function(e){return e+"-oji"},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("lt","lt",{closeText:"Uždaryti",prevText:"<Atgal",nextText:"Pirmyn>",currentText:"Šiandien",monthNames:["Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis","Liepa","Rugpjūtis","Rugsėjis","Spalis","Lapkritis","Gruodis"],monthNamesShort:["Sau","Vas","Kov","Bal","Geg","Bir","Lie","Rugp","Rugs","Spa","Lap","Gru"],dayNames:["sekmadienis","pirmadienis","antradienis","trečiadienis","ketvirtadienis","penktadienis","šeštadienis"],dayNamesShort:["sek","pir","ant","tre","ket","pen","šeš"],dayNamesMin:["Se","Pr","An","Tr","Ke","Pe","Še"],weekHeader:"SAV",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),e.fullCalendar.lang("lt",{defaultButtonText:{month:"Mėnuo",week:"Savaitė",day:"Diena",list:"Darbotvarkė"},allDayText:"Visą dieną",eventLimitText:"daugiau"})}(),function(){function a(e,t,a){var n=e.split("_");return a?1===t%10&&11!==t?n[2]:n[3]:1===t%10&&11!==t?n[0]:n[1]}function n(e,t,n){return e+" "+a(r[n],e,t)}var r={mm:"minūti_minūtes_minūte_minūtes",hh:"stundu_stundas_stunda_stundas",dd:"dienu_dienas_diena_dienas",MM:"mēnesi_mēnešus_mēnesis_mēneši",yy:"gadu_gadus_gads_gadi"};(t.defineLocale||t.lang).call(t,"lv",{months:"janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris".split("_"),monthsShort:"jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec".split("_"),weekdays:"svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena".split("_"),weekdaysShort:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysMin:"Sv_P_O_T_C_Pk_S".split("_"),longDateFormat:{LT:"HH:mm",L:"DD.MM.YYYY",LL:"YYYY. [gada] D. MMMM",LLL:"YYYY. [gada] D. MMMM, LT",LLLL:"YYYY. [gada] D. MMMM, dddd, LT"},calendar:{sameDay:"[Šodien pulksten] LT",nextDay:"[Rīt pulksten] LT",nextWeek:"dddd [pulksten] LT",lastDay:"[Vakar pulksten] LT",lastWeek:"[Pagājušā] dddd [pulksten] LT",sameElse:"L"},relativeTime:{future:"%s vēlāk",past:"%s agrāk",s:"dažas sekundes",m:"minūti",mm:n,h:"stundu",hh:n,d:"dienu",dd:n,M:"mēnesi",MM:n,y:"gadu",yy:n},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("lv","lv",{closeText:"Aizvērt",prevText:"Iepr.",nextText:"Nāk.",currentText:"Šodien",monthNames:["Janvāris","Februāris","Marts","Aprīlis","Maijs","Jūnijs","Jūlijs","Augusts","Septembris","Oktobris","Novembris","Decembris"],monthNamesShort:["Jan","Feb","Mar","Apr","Mai","Jūn","Jūl","Aug","Sep","Okt","Nov","Dec"],dayNames:["svētdiena","pirmdiena","otrdiena","trešdiena","ceturtdiena","piektdiena","sestdiena"],dayNamesShort:["svt","prm","otr","tre","ctr","pkt","sst"],dayNamesMin:["Sv","Pr","Ot","Tr","Ct","Pk","Ss"],weekHeader:"Ned.",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("lv",{defaultButtonText:{month:"Mēnesis",week:"Nedēļa",day:"Diena",list:"Dienas kārtība"},allDayText:"Visu dienu",eventLimitText:function(e){return"+vēl "+e}})}(),function(){var a="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),n="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(t.defineLocale||t.lang).call(t,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,t){return/-MMM-/.test(t)?n[e.month()]:a[e.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("nl",{defaultButtonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})}(),function(){function a(e){return 5>e%10&&e%10>1&&1!==~~(e/10)%10}function n(e,t,n){var r=e+" ";switch(n){case"m":return t?"minuta":"minutę";case"mm":return r+(a(e)?"minuty":"minut");case"h":return t?"godzina":"godzinę";case"hh":return r+(a(e)?"godziny":"godzin");case"MM":return r+(a(e)?"miesiące":"miesięcy");case"yy":return r+(a(e)?"lata":"lat")}}var r="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),i="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(t.defineLocale||t.lang).call(t,"pl",{months:function(e,t){return/D MMMM/.test(t)?i[e.month()]:r[e.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:n,mm:n,h:n,hh:n,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:n,y:"rok",yy:n},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("pl",{defaultButtonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})}(),function(){(t.defineLocale||t.lang).call(t,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinal:"%dº"}),e.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("pt-br",{defaultButtonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(e){return"mais +"+e}})}(),function(){(t.defineLocale||t.lang).call(t,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinal:"%dº",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("pt",{defaultButtonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})}(),function(){function a(e,t,a){var n={mm:"minute",hh:"ore",dd:"zile",MM:"luni",yy:"ani"},r=" ";return(e%100>=20||e>=100&&0===e%100)&&(r=" de "),e+r+n[a]}(t.defineLocale||t.lang).call(t,"ro",{months:"ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie".split("_"),monthsShort:"ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.".split("_"),weekdays:"duminică_luni_marți_miercuri_joi_vineri_sâmbătă".split("_"),weekdaysShort:"Dum_Lun_Mar_Mie_Joi_Vin_Sâm".split("_"),weekdaysMin:"Du_Lu_Ma_Mi_Jo_Vi_Sâ".split("_"),longDateFormat:{LT:"H:mm",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[azi la] LT",nextDay:"[mâine la] LT",nextWeek:"dddd [la] LT",lastDay:"[ieri la] LT",lastWeek:"[fosta] dddd [la] LT",sameElse:"L"},relativeTime:{future:"peste %s",past:"%s în urmă",s:"câteva secunde",m:"un minut",mm:a,h:"o oră",hh:a,d:"o zi",dd:a,M:"o lună",MM:a,y:"un an",yy:a},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("ro","ro",{closeText:"Închide",prevText:"« Luna precedentă",nextText:"Luna următoare »",currentText:"Azi",monthNames:["Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie","Iulie","August","Septembrie","Octombrie","Noiembrie","Decembrie"],monthNamesShort:["Ian","Feb","Mar","Apr","Mai","Iun","Iul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Duminică","Luni","Marţi","Miercuri","Joi","Vineri","Sâmbătă"],dayNamesShort:["Dum","Lun","Mar","Mie","Joi","Vin","Sâm"],dayNamesMin:["Du","Lu","Ma","Mi","Jo","Vi","Sâ"],weekHeader:"Săpt",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ro",{defaultButtonText:{prev:"precedentă",next:"următoare",month:"Lună",week:"Săptămână",day:"Zi",list:"Agendă"},allDayText:"Toată ziua",eventLimitText:function(e){return"+alte "+e +}})}(),function(){function a(e,t){var a=e.split("_");return 1===t%10&&11!==t%100?a[0]:t%10>=2&&4>=t%10&&(10>t%100||t%100>=20)?a[1]:a[2]}function n(e,t,n){var r={mm:t?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===n?t?"минута":"минуту":e+" "+a(r[n],+e)}function r(e,t){var a={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},n=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return a[n][e.month()]}function i(e,t){var a={nominative:"янв_фев_мар_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},n=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return a[n][e.month()]}function s(e,t){var a={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},n=/\[ ?[Вв] ?(?:прошлую|следующую)? ?\] ?dddd/.test(t)?"accusative":"nominative";return a[n][e.day()]}(t.defineLocale||t.lang).call(t,"ru",{months:r,monthsShort:i,weekdays:s,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(){switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:n,mm:n,h:"час",hh:n,d:"день",dd:n,M:"месяц",MM:n,y:"год",yy:n},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(e){return/^(дня|вечера)$/.test(e)},meridiem:function(e){return 4>e?"ночи":12>e?"утра":17>e?"дня":"вечера"},ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":return e+"-й";case"D":return e+"-го";case"w":case"W":return e+"-я";default:return e}},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ru",{defaultButtonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(e){return"+ ещё "+e}})}(),function(){function a(e){return e>1&&5>e}function n(e,t,n,r){var i=e+" ";switch(n){case"s":return t||r?"pár sekúnd":"pár sekundami";case"m":return t?"minúta":r?"minútu":"minútou";case"mm":return t||r?i+(a(e)?"minúty":"minút"):i+"minútami";case"h":return t?"hodina":r?"hodinu":"hodinou";case"hh":return t||r?i+(a(e)?"hodiny":"hodín"):i+"hodinami";case"d":return t||r?"deň":"dňom";case"dd":return t||r?i+(a(e)?"dni":"dní"):i+"dňami";case"M":return t||r?"mesiac":"mesiacom";case"MM":return t||r?i+(a(e)?"mesiace":"mesiacov"):i+"mesiacmi";case"y":return t||r?"rok":"rokom";case"yy":return t||r?i+(a(e)?"roky":"rokov"):i+"rokmi"}}var r="január_február_marec_apríl_máj_jún_júl_august_september_október_november_december".split("_"),i="jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec".split("_");(t.defineLocale||t.lang).call(t,"sk",{months:r,monthsShort:i,monthsParse:function(e,t){var a,n=[];for(a=0;12>a;a++)n[a]=RegExp("^"+e[a]+"$|^"+t[a]+"$","i");return n}(r,i),weekdays:"nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota".split("_"),weekdaysShort:"ne_po_ut_st_št_pi_so".split("_"),weekdaysMin:"ne_po_ut_st_št_pi_so".split("_"),longDateFormat:{LT:"H:mm",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes o] LT",nextDay:"[zajtra o] LT",nextWeek:function(){switch(this.day()){case 0:return"[v nedeľu o] LT";case 1:case 2:return"[v] dddd [o] LT";case 3:return"[v stredu o] LT";case 4:return"[vo štvrtok o] LT";case 5:return"[v piatok o] LT";case 6:return"[v sobotu o] LT"}},lastDay:"[včera o] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulú nedeľu o] LT";case 1:case 2:return"[minulý] dddd [o] LT";case 3:return"[minulú stredu o] LT";case 4:case 5:return"[minulý] dddd [o] LT";case 6:return"[minulú sobotu o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"pred %s",s:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("sk","sk",{closeText:"Zavrieť",prevText:"<Predchádzajúci",nextText:"Nasledujúci>",currentText:"Dnes",monthNames:["január","február","marec","apríl","máj","jún","júl","august","september","október","november","december"],monthNamesShort:["Jan","Feb","Mar","Apr","Máj","Jún","Júl","Aug","Sep","Okt","Nov","Dec"],dayNames:["nedeľa","pondelok","utorok","streda","štvrtok","piatok","sobota"],dayNamesShort:["Ned","Pon","Uto","Str","Štv","Pia","Sob"],dayNamesMin:["Ne","Po","Ut","St","Št","Pia","So"],weekHeader:"Ty",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sk",{defaultButtonText:{month:"Mesiac",week:"Týždeň",day:"Deň",list:"Rozvrh"},allDayText:"Celý deň",eventLimitText:function(e){return"+ďalšie: "+e}})}(),function(){function a(e,t,a){var n=e+" ";switch(a){case"m":return t?"ena minuta":"eno minuto";case"mm":return n+=1===e?"minuta":2===e?"minuti":3===e||4===e?"minute":"minut";case"h":return t?"ena ura":"eno uro";case"hh":return n+=1===e?"ura":2===e?"uri":3===e||4===e?"ure":"ur";case"dd":return n+=1===e?"dan":"dni";case"MM":return n+=1===e?"mesec":2===e?"meseca":3===e||4===e?"mesece":"mesecev";case"yy":return n+=1===e?"leto":2===e?"leti":3===e||4===e?"leta":"let"}}(t.defineLocale||t.lang).call(t,"sl",{months:"januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"),weekdays:"nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota".split("_"),weekdaysShort:"ned._pon._tor._sre._čet._pet._sob.".split("_"),weekdaysMin:"ne_po_to_sr_če_pe_so".split("_"),longDateFormat:{LT:"H:mm",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danes ob] LT",nextDay:"[jutri ob] LT",nextWeek:function(){switch(this.day()){case 0:return"[v] [nedeljo] [ob] LT";case 3:return"[v] [sredo] [ob] LT";case 6:return"[v] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[v] dddd [ob] LT"}},lastDay:"[včeraj ob] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[prejšnja] dddd [ob] LT";case 1:case 2:case 4:case 5:return"[prejšnji] dddd [ob] LT"}},sameElse:"L"},relativeTime:{future:"čez %s",past:"%s nazaj",s:"nekaj sekund",m:a,mm:a,h:a,hh:a,d:"en dan",dd:a,M:"en mesec",MM:a,y:"eno leto",yy:a},ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("sl","sl",{closeText:"Zapri",prevText:"<Prejšnji",nextText:"Naslednji>",currentText:"Trenutni",monthNames:["Januar","Februar","Marec","April","Maj","Junij","Julij","Avgust","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],dayNames:["Nedelja","Ponedeljek","Torek","Sreda","Četrtek","Petek","Sobota"],dayNamesShort:["Ned","Pon","Tor","Sre","Čet","Pet","Sob"],dayNamesMin:["Ne","Po","To","Sr","Če","Pe","So"],weekHeader:"Teden",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sl",{defaultButtonText:{month:"Mesec",week:"Teden",day:"Dan",list:"Dnevni red"},allDayText:"Ves dan",eventLimitText:"več"})}(),function(){var a={words:{m:["један минут","једне минуте"],mm:["минут","минуте","минута"],h:["један сат","једног сата"],hh:["сат","сата","сати"],dd:["дан","дана","дана"],MM:["месец","месеца","месеци"],yy:["година","године","година"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&4>=e?t[1]:t[2]},translate:function(e,t,n){var r=a.words[n];return 1===n.length?t?r[0]:r[1]:e+" "+a.correctGrammaticalCase(e,r)}};(t.defineLocale||t.lang).call(t,"sr-cyrl",{months:["јануар","фебруар","март","април","мај","јун","јул","август","септембар","октобар","новембар","децембар"],monthsShort:["јан.","феб.","мар.","апр.","мај","јун","јул","авг.","сеп.","окт.","нов.","дец."],weekdays:["недеља","понедељак","уторак","среда","четвртак","петак","субота"],weekdaysShort:["нед.","пон.","уто.","сре.","чет.","пет.","суб."],weekdaysMin:["не","по","ут","ср","че","пе","су"],longDateFormat:{LT:"H:mm",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[данас у] LT",nextDay:"[сутра у] LT",nextWeek:function(){switch(this.day()){case 0:return"[у] [недељу] [у] LT";case 3:return"[у] [среду] [у] LT";case 6:return"[у] [суботу] [у] LT";case 1:case 2:case 4:case 5:return"[у] dddd [у] LT"}},lastDay:"[јуче у] LT",lastWeek:function(){var e=["[прошле] [недеље] [у] LT","[прошлог] [понедељка] [у] LT","[прошлог] [уторка] [у] LT","[прошле] [среде] [у] LT","[прошлог] [четвртка] [у] LT","[прошлог] [петка] [у] LT","[прошле] [суботе] [у] LT"];return e[this.day()]},sameElse:"L"},relativeTime:{future:"за %s",past:"пре %s",s:"неколико секунди",m:a.translate,mm:a.translate,h:a.translate,hh:a.translate,d:"дан",dd:a.translate,M:"месец",MM:a.translate,y:"годину",yy:a.translate},ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("sr-cyrl","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sr-cyrl",{defaultButtonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(e){return"+ још "+e}})}(),function(){var a={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&4>=e?t[1]:t[2]},translate:function(e,t,n){var r=a.words[n];return 1===n.length?t?r[0]:r[1]:e+" "+a.correctGrammaticalCase(e,r)}};(t.defineLocale||t.lang).call(t,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var e=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return e[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:a.translate,mm:a.translate,h:a.translate,hh:a.translate,d:"dan",dd:a.translate,M:"mesec",MM:a.translate,y:"godinu",yy:a.translate},ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sr",{defaultButtonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(e){return"+ још "+e}})}(),function(){(t.defineLocale||t.lang).call(t,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinal:function(e){var t=e%10,a=1===~~(e%100/10)?"e":1===t?"a":2===t?"a":3===t?"e":"e";return e+a},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sv",{defaultButtonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})}(),function(){(t.defineLocale||t.lang).call(t,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiem:function(e){return 12>e?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),e.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("th",{defaultButtonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})}(),function(){var a={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(t.defineLocale||t.lang).call(t,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinal:function(e){if(0===e)return e+"'ıncı";var t=e%10,n=e%100-t,r=e>=100?100:null;return e+(a[t]||a[n]||a[r])},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("tr",{defaultButtonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})}(),function(){function a(e,t){var a=e.split("_");return 1===t%10&&11!==t%100?a[0]:t%10>=2&&4>=t%10&&(10>t%100||t%100>=20)?a[1]:a[2]}function n(e,t,n){var r={mm:"хвилина_хвилини_хвилин",hh:"година_години_годин",dd:"день_дні_днів",MM:"місяць_місяці_місяців",yy:"рік_роки_років"};return"m"===n?t?"хвилина":"хвилину":"h"===n?t?"година":"годину":e+" "+a(r[n],+e)}function r(e,t){var a={nominative:"січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень".split("_"),accusative:"січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня".split("_")},n=/D[oD]? *MMMM?/.test(t)?"accusative":"nominative";return a[n][e.month()]}function i(e,t){var a={nominative:"неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота".split("_"),accusative:"неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу".split("_"),genitive:"неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи".split("_")},n=/(\[[ВвУу]\]) ?dddd/.test(t)?"accusative":/\[?(?:минулої|наступної)? ?\] ?dddd/.test(t)?"genitive":"nominative";return a[n][e.day()]}function s(e){return function(){return e+"о"+(11===this.hours()?"б":"")+"] LT"}}(t.defineLocale||t.lang).call(t,"uk",{months:r,monthsShort:"січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд".split("_"),weekdays:i,weekdaysShort:"нд_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"нд_пн_вт_ср_чт_пт_сб".split("_"),longDateFormat:{LT:"HH:mm",L:"DD.MM.YYYY",LL:"D MMMM YYYY р.",LLL:"D MMMM YYYY р., LT",LLLL:"dddd, D MMMM YYYY р., LT"},calendar:{sameDay:s("[Сьогодні "),nextDay:s("[Завтра "),lastDay:s("[Вчора "),nextWeek:s("[У] dddd ["),lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return s("[Минулої] dddd [").call(this);case 1:case 2:case 4:return s("[Минулого] dddd [").call(this)}},sameElse:"L"},relativeTime:{future:"за %s",past:"%s тому",s:"декілька секунд",m:n,mm:n,h:"годину",hh:n,d:"день",dd:n,M:"місяць",MM:n,y:"рік",yy:n},meridiem:function(e){return 4>e?"ночі":12>e?"ранку":17>e?"дня":"вечора"},ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":case"w":case"W":return e+"-й";case"D":return e+"-го";default:return e}},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("uk","uk",{closeText:"Закрити",prevText:"<",nextText:">",currentText:"Сьогодні",monthNames:["Січень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","Вересень","Жовтень","Листопад","Грудень"],monthNamesShort:["Січ","Лют","Бер","Кві","Тра","Чер","Лип","Сер","Вер","Жов","Лис","Гру"],dayNames:["неділя","понеділок","вівторок","середа","четвер","п’ятниця","субота"],dayNamesShort:["нед","пнд","вів","срд","чтв","птн","сбт"],dayNamesMin:["Нд","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Тиж",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("uk",{defaultButtonText:{month:"Місяць",week:"Тиждень",day:"День",list:"Порядок денний"},allDayText:"Увесь день",eventLimitText:function(e){return"+ще "+e+"..."}})}(),function(){(t.defineLocale||t.lang).call(t,"vi",{months:"tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12".split("_"),monthsShort:"Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12".split("_"),weekdays:"chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ sáu_thứ bảy".split("_"),weekdaysShort:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysMin:"CN_T2_T3_T4_T5_T6_T7".split("_"),longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM [năm] YYYY",LLL:"D MMMM [năm] YYYY LT",LLLL:"dddd, D MMMM [năm] YYYY LT",l:"DD/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY LT",llll:"ddd, D MMM YYYY LT"},calendar:{sameDay:"[Hôm nay lúc] LT",nextDay:"[Ngày mai lúc] LT",nextWeek:"dddd [tuần tới lúc] LT",lastDay:"[Hôm qua lúc] LT",lastWeek:"dddd [tuần rồi lúc] LT",sameElse:"L"},relativeTime:{future:"%s tới",past:"%s trước",s:"vài giây",m:"một phút",mm:"%d phút",h:"một giờ",hh:"%d giờ",d:"một ngày",dd:"%d ngày",M:"một tháng",MM:"%d tháng",y:"một năm",yy:"%d năm"},ordinal:function(e){return e},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("vi","vi",{closeText:"Đóng",prevText:"<Trước",nextText:"Tiếp>",currentText:"Hôm nay",monthNames:["Tháng Một","Tháng Hai","Tháng Ba","Tháng Tư","Tháng Năm","Tháng Sáu","Tháng Bảy","Tháng Tám","Tháng Chín","Tháng Mười","Tháng Mười Một","Tháng Mười Hai"],monthNamesShort:["Tháng 1","Tháng 2","Tháng 3","Tháng 4","Tháng 5","Tháng 6","Tháng 7","Tháng 8","Tháng 9","Tháng 10","Tháng 11","Tháng 12"],dayNames:["Chủ Nhật","Thứ Hai","Thứ Ba","Thứ Tư","Thứ Năm","Thứ Sáu","Thứ Bảy"],dayNamesShort:["CN","T2","T3","T4","T5","T6","T7"],dayNamesMin:["CN","T2","T3","T4","T5","T6","T7"],weekHeader:"Tu",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("vi",{defaultButtonText:{month:"Tháng",week:"Tuần",day:"Ngày",list:"Lịch biểu"},allDayText:"Cả ngày",eventLimitText:function(e){return"+ thêm "+e}})}(),function(){(t.defineLocale||t.lang).call(t,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiem:function(e,t){var a=100*e+t;return 600>a?"凌晨":900>a?"早上":1130>a?"上午":1230>a?"中午":1800>a?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var e,a;return e=t().startOf("week"),a=this.unix()-e.unix()>=604800?"[下]":"[本]",0===this.minutes()?a+"dddAh点整":a+"dddAh点mm"},lastWeek:function(){var e,a;return e=t().startOf("week"),a=this.unix()a?"早上":1130>a?"上午":1230>a?"中午":1800>a?"下午":"晚上"},calendar:{sameDay:"[今天]LT",nextDay:"[明天]LT",nextWeek:"[下]ddddLT",lastDay:"[昨天]LT",lastWeek:"[上]ddddLT",sameElse:"L"},ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"週";default:return e}},relativeTime:{future:"%s內",past:"%s前",s:"幾秒",m:"一分鐘",mm:"%d分鐘",h:"一小時",hh:"%d小時",d:"一天",dd:"%d天",M:"一個月",MM:"%d個月",y:"一年",yy:"%d年"}}),e.fullCalendar.datepickerLang("zh-tw","zh-TW",{closeText:"關閉",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy/mm/dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),e.fullCalendar.lang("zh-tw",{defaultButtonText:{month:"月",week:"週",day:"天",list:"待辦事項"},allDayText:"全天",eventLimitText:"更多"})}(),(t.locale||t.lang).call(t,"en"),e.fullCalendar.lang("en"),e.datepicker&&e.datepicker.setDefaults(e.datepicker.regional[""])}); \ No newline at end of file diff --git a/app/bundles/CoreBundle/Cache/MiddlewareCacheWarmer.php b/app/bundles/CoreBundle/Cache/MiddlewareCacheWarmer.php index 1f834a40b9c..e143cbd2ec8 100644 --- a/app/bundles/CoreBundle/Cache/MiddlewareCacheWarmer.php +++ b/app/bundles/CoreBundle/Cache/MiddlewareCacheWarmer.php @@ -1,14 +1,6 @@ coreParametersHelper->get('composer_updates', false)) { + $output->writeln(''.$this->translator->trans('mautic.core.command.update.composer').''); + + return 1; + } + try { if (empty($options['finish'])) { $returnCode = $this->startUpgrade(); diff --git a/app/bundles/CoreBundle/Command/CleanupMaintenanceCommand.php b/app/bundles/CoreBundle/Command/CleanupMaintenanceCommand.php index 4bb118c5475..832ab42ef96 100644 --- a/app/bundles/CoreBundle/Command/CleanupMaintenanceCommand.php +++ b/app/bundles/CoreBundle/Command/CleanupMaintenanceCommand.php @@ -1,14 +1,5 @@ [ 'main' => [ @@ -159,6 +150,12 @@ 'mautic.core.model.notification', ], ], + 'mautic.core.service.local_file_adapter' => [ + 'class' => \Mautic\CoreBundle\Service\LocalFileAdapterService::class, + 'arguments' => [ + '%env(resolve:MAUTIC_EL_FINDER_PATH)%', + ], + ], ], 'events' => [ 'mautic.core.subscriber' => [ @@ -855,6 +852,7 @@ '%kernel.cache_dir%', 'session', 'mautic.helper.paths', + 'kernel', ], ], 'mautic.helper.templating' => [ @@ -910,6 +908,13 @@ 'translator', ], ], + 'mautic.helper.composer' => [ + 'class' => \Mautic\CoreBundle\Helper\ComposerHelper::class, + 'arguments' => [ + 'kernel', + 'monolog.logger.mautic', + ], + ], // Menu 'mautic.helper.menu' => [ 'class' => 'Mautic\CoreBundle\Menu\MenuHelper', @@ -926,6 +931,10 @@ 'mautic.helper.random' => [ 'class' => \Mautic\CoreBundle\Helper\RandomHelper\RandomHelper::class, ], + 'mautic.helper.command' => [ + 'class' => \Mautic\CoreBundle\Helper\CommandHelper::class, + 'arguments' => 'kernel', + ], 'mautic.menu_renderer' => [ 'class' => \Mautic\CoreBundle\Menu\MenuRenderer::class, 'arguments' => [ @@ -1744,5 +1753,6 @@ 'font' => 'メイリオ, Meiryo, MS Pゴシック, MS PGothic, ヒラギノ角ゴ Pro W3, Hiragino Kaku Gothic Pro,Osaka, sans-serif', ], ], + 'composer_updates' => false, ], ]; diff --git a/app/bundles/CoreBundle/Configurator/Configurator.php b/app/bundles/CoreBundle/Configurator/Configurator.php index ac235018660..3aa9bc54dc9 100644 --- a/app/bundles/CoreBundle/Configurator/Configurator.php +++ b/app/bundles/CoreBundle/Configurator/Configurator.php @@ -1,14 +1,5 @@ get('mautic.'.$this->getSessionBase().'.orderby', $repo->getTableAlias().'.'.$this->getDefaultOrderColumn()); $orderByDir = $session->get('mautic.'.$this->getSessionBase().'.orderbydir', $this->getDefaultOrderDirection()); - list($count, $items) = $this->getIndexItems($start, $limit, $filter, $orderBy, $orderByDir); + [$count, $items] = $this->getIndexItems($start, $limit, $filter, $orderBy, $orderByDir); if ($count && $count < ($start + 1)) { //the number of entities are now less then the current page so redirect to the last page diff --git a/app/bundles/CoreBundle/Controller/AjaxController.php b/app/bundles/CoreBundle/Controller/AjaxController.php index 22d570b59c9..d897c411bd2 100644 --- a/app/bundles/CoreBundle/Controller/AjaxController.php +++ b/app/bundles/CoreBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ container->get('mautic.helper.cookie'); /** @var \Mautic\CoreBundle\Helper\UpdateHelper $updateHelper */ $updateHelper = $this->container->get('mautic.helper.update'); + /** @var CoreParametersHelper $coreParametersHelper */ + $coreParametersHelper = $this->container->get('mautic.helper.core_parameters'); + $errors = []; - $results = $updateHelper->runPreUpdateChecks(); - $errors = []; + if (true === $coreParametersHelper->get('composer_updates', false)) { + $errors = [$translator->trans('mautic.core.update.composer')]; + } else { + $results = $updateHelper->runPreUpdateChecks(); - foreach ($results as $result) { - if (!$result->success) { - $errors = array_merge($errors, array_map(fn (PreUpdateCheckError $error) => $translator->trans($error->key, $error->parameters), $result->errors)); + foreach ($results as $result) { + if (!$result->success) { + $errors = array_merge($errors, array_map(fn (PreUpdateCheckError $error) => $translator->trans($error->key, $error->parameters), $result->errors)); + } } } diff --git a/app/bundles/CoreBundle/Controller/AjaxLookupControllerTrait.php b/app/bundles/CoreBundle/Controller/AjaxLookupControllerTrait.php index 3ccf5b4172c..643f44209c7 100644 --- a/app/bundles/CoreBundle/Controller/AjaxLookupControllerTrait.php +++ b/app/bundles/CoreBundle/Controller/AjaxLookupControllerTrait.php @@ -1,14 +1,5 @@ request->query->has('orderby') && false === $session->has("$name.orderbydir")) { + $session->set("$name.orderbydir", $this->getDefaultOrderDirection()); + } + if ($this->request->query->has('orderby')) { $orderBy = InputHelper::clean($this->request->query->get('orderby'), true); $dir = $session->get("$name.orderbydir", 'ASC'); - $dir = ('ASC' == $dir) ? 'DESC' : 'ASC'; + $dir = $orderBy === $session->get("$name.orderby") || false == $session->has("$name.orderby") ? (('ASC' == $dir) ? 'DESC' : 'ASC') : $dir; $session->set("$name.orderby", $orderBy); $session->set("$name.orderbydir", $dir); } @@ -607,7 +602,7 @@ protected function getNotificationContent(Request $request = null) /** @var \Mautic\CoreBundle\Model\NotificationModel $model */ $model = $this->getModel('core.notification'); - list($notifications, $showNewIndicator, $updateMessage) = $model->getNotificationContent($afterId, false, 200); + [$notifications, $showNewIndicator, $updateMessage] = $model->getNotificationContent($afterId, false, 200); $lastNotification = reset($notifications); @@ -644,8 +639,6 @@ public function addNotification($message, $type = null, $isRead = true, $header * @param string|null $level * @param string|null $domain * @param bool|null $addNotification - * - * @deprecated Will be removed in Mautic 3.0. Use CommonController::flashBag->addFlash() instead. */ public function addFlash($message, $messageVars = [], $level = FlashBag::LEVEL_NOTICE, $domain = 'flashes', $addNotification = false) { @@ -768,4 +761,12 @@ protected function getDataForExport(AbstractCommonModel $model, array $args, cal return $data->getDataForExport($start, $model, $args, $resultsCallback); } + + /** + * @return string + */ + protected function getDefaultOrderDirection() + { + return 'ASC'; + } } diff --git a/app/bundles/CoreBundle/Controller/DefaultController.php b/app/bundles/CoreBundle/Controller/DefaultController.php index a496cf0f5b9..4ce33d76a2e 100644 --- a/app/bundles/CoreBundle/Controller/DefaultController.php +++ b/app/bundles/CoreBundle/Controller/DefaultController.php @@ -1,14 +1,5 @@ deprecatedSessionBase; + return null !== $this->deprecatedSessionBase ? $this->deprecatedSessionBase : parent::getSessionBase($objectId); } /** diff --git a/app/bundles/CoreBundle/Controller/FormErrorMessagesTrait.php b/app/bundles/CoreBundle/Controller/FormErrorMessagesTrait.php index 9ad445ccd38..60ebba4038c 100644 --- a/app/bundles/CoreBundle/Controller/FormErrorMessagesTrait.php +++ b/app/bundles/CoreBundle/Controller/FormErrorMessagesTrait.php @@ -1,14 +1,5 @@ $form + * @param string $template + * @param mixed $themes * * @return \Symfony\Component\Form\FormView */ - protected function setFormTheme(Form $form, $template, $themes = null) + protected function setFormTheme(FormInterface $form, $template, $themes = null) { $formView = $form->createView(); diff --git a/app/bundles/CoreBundle/Controller/JsController.php b/app/bundles/CoreBundle/Controller/JsController.php index ab767c90606..7556b9f5863 100644 --- a/app/bundles/CoreBundle/Controller/JsController.php +++ b/app/bundles/CoreBundle/Controller/JsController.php @@ -1,14 +1,5 @@ container->get('mautic.helper.update'); $updateData = $updateHelper->fetchData(); + /** @var CoreParametersHelper $coreParametersHelper */ + $coreParametersHelper = $this->container->get('mautic.helper.core_parameters'); return $this->delegateView([ 'viewParameters' => [ - 'updateData' => $updateData, - 'currentVersion' => MAUTIC_VERSION, + 'updateData' => $updateData, + 'currentVersion' => MAUTIC_VERSION, + 'isComposerEnabled' => $coreParametersHelper->get('composer_updates', false), ], 'contentTemplate' => 'MauticCoreBundle:Update:index.html.php', 'passthroughVars' => [ diff --git a/app/bundles/CoreBundle/Controller/VariantAjaxControllerTrait.php b/app/bundles/CoreBundle/Controller/VariantAjaxControllerTrait.php index 9e59021a441..8f6f1373abb 100644 --- a/app/bundles/CoreBundle/Controller/VariantAjaxControllerTrait.php +++ b/app/bundles/CoreBundle/Controller/VariantAjaxControllerTrait.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - - Modified for - * @package Mautic - * @copyright 2014 Mautic Contributors. All rights reserved. - * @author Mautic - * @link http://mautic.org - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\DependencyInjection\Compiler; use Mautic\CoreBundle\Templating\Engine\PhpEngine; diff --git a/app/bundles/CoreBundle/DependencyInjection/Compiler/TranslationsPass.php b/app/bundles/CoreBundle/DependencyInjection/Compiler/TranslationsPass.php index 07aafe34db2..ee2ab320df6 100644 --- a/app/bundles/CoreBundle/DependencyInjection/Compiler/TranslationsPass.php +++ b/app/bundles/CoreBundle/DependencyInjection/Compiler/TranslationsPass.php @@ -1,14 +1,5 @@ value = $value; + $this->booleanMode = $booleanMode; + $this->wordSearch = $wordSearch; + $this->wordInflecting = $wordInflecting; + } + + public function __toString(): string + { + return $this->format(); + } + + public function format(): string + { + $return = ''; + $value = mb_substr($this->value, 0, 255); + + if ($this->wordSearch) { + $words = explode(' ', preg_replace('/[^\p{L}\p{N}_]+/u', ' ', $value)); + $wordCount = count($words); + + for ($i = 0; $i < $wordCount; ++$i) { + $word = $words[$i]; + + if ($this->booleanMode) { + // strip boolean operators + $word = str_replace(['+', '-', '@', '<', '>', '(', ')', '~', '*', '"'], '', $word); + } + + $wordLength = mb_strlen($word); + + if ($wordLength > 0) { + if ($this->booleanMode) { + if ($this->wordInflecting && $wordLength > 3) { + $return .= '+('.$word.'* <'.mb_substr($word, 0, $wordLength - 1).'*)'; + } else { + $return .= '+'.$word.'*'; + } + } else { + $return .= $word; + } + + $return .= ' '; + } + } + + $return = trim($return); + } + + // append phrase search with a higher rank + if ($this->booleanMode && $value) { + $return = sprintf('%s"%s"', $return ? '('.$return.') >' : '', trim(str_replace('"', "'", $value))); + } + + return $return; + } +} diff --git a/app/bundles/CoreBundle/Doctrine/Helper/IndexSchemaHelper.php b/app/bundles/CoreBundle/Doctrine/Helper/IndexSchemaHelper.php index 085862091e7..acc0f58ab26 100644 --- a/app/bundles/CoreBundle/Doctrine/Helper/IndexSchemaHelper.php +++ b/app/bundles/CoreBundle/Doctrine/Helper/IndexSchemaHelper.php @@ -1,14 +1,5 @@ getClassMetadata(); @@ -448,16 +436,42 @@ public function addPartialIndex(array $columns, $name, $where) $cm->table['indexes'] = []; } - $cm->table['indexes'][$name] = [' - columns' => $columns, - 'options' => [ - 'where' => $where, - ], - ]; + $definition = ['columns' => $columns]; + + if (null !== $flags) { + $definition['flags'] = $flags; + } + + if (null !== $options) { + $definition['options'] = $options; + } + + $cm->table['indexes'][$name] = $definition; return $this; } + /** + * @deprecated this method will be removed as MySQL does not support partial indices whatsoever + * + * @param string $name + * @param string $where + * + * @return self + */ + public function addPartialIndex(array $columns, $name, $where) + { + return $this->addIndex($columns, $name, null, ['where' => $where]); + } + + /** + * @param mixed[] $columns + */ + public function addFulltextIndex(array $columns, string $name): self + { + return $this->addIndex($columns, $name, ['fulltext']); + } + /** * UTF8MB4 encoding needs max length of 191 instead of 255 that UTF8 needed. Doctrine does not take care of it by itself. */ @@ -465,4 +479,22 @@ public function isIndexedVarchar(string $name, string $type): bool { return Types::STRING === $type || isset($this->getClassMetadata()->table['indexes'][$name]); } + + /** + * Adds Index with options. + * + * @param list $columns + * @param array $options + */ + public function addIndexWithOptions(array $columns, string $name, array $options): ClassMetadataBuilder + { + $cm = $this->getClassMetadata(); + + $cm->table['indexes'][$name] = [ + 'columns' => $columns, + 'options' => $options, + ]; + + return $this; + } } diff --git a/app/bundles/CoreBundle/Doctrine/Mapping/ManyToManyAssociationBuilder.php b/app/bundles/CoreBundle/Doctrine/Mapping/ManyToManyAssociationBuilder.php index 0674b9ef047..6979b5a56f6 100644 --- a/app/bundles/CoreBundle/Doctrine/Mapping/ManyToManyAssociationBuilder.php +++ b/app/bundles/CoreBundle/Doctrine/Mapping/ManyToManyAssociationBuilder.php @@ -1,14 +1,5 @@ add( + 'composer_updates', + YesNoButtonGroupType::class, + [ + 'label' => 'mautic.core.config.form.update.composer', + 'data' => (array_key_exists('composer_updates', $options['data']) && !empty($options['data']['composer_updates'])), + 'attr' => [ + 'class' => 'form-control', + 'tooltip' => 'mautic.core.config.form.update.composer.tooltip', + ], + ] + ); + $builder->add( 'locale', ChoiceType::class, diff --git a/app/bundles/CoreBundle/Form/Type/CountryType.php b/app/bundles/CoreBundle/Form/Type/CountryType.php index 6e819ea68ff..77286390f23 100644 --- a/app/bundles/CoreBundle/Form/Type/CountryType.php +++ b/app/bundles/CoreBundle/Form/Type/CountryType.php @@ -1,14 +1,5 @@ getData(); - /** @var DynamicContentEntityTrait $entity */ + /** @var Email $entity */ $entity = $event->getForm()->getData(); if (empty($data['dynamicContent'])) { $data['dynamicContent'] = $entity->getDefaultDynamicContent(); unset($data['dynamicContent'][0]['filters']['filter']); - $event->setData($data); } + + foreach ($data['dynamicContent'] as $key => $dc) { + if (empty($dc['filters'])) { + $data['dynamicContent'][$key]['filters'] = $entity->getDefaultDynamicContent()[0]['filters']; + } + } + + $event->setData($data); } ); } diff --git a/app/bundles/CoreBundle/Form/Type/DynamicListType.php b/app/bundles/CoreBundle/Form/Type/DynamicListType.php index 9102a6ecad5..f9904531282 100644 --- a/app/bundles/CoreBundle/Form/Type/DynamicListType.php +++ b/app/bundles/CoreBundle/Form/Type/DynamicListType.php @@ -1,14 +1,5 @@ addEventListener( FormEvents::PRE_SUBMIT, function (FormEvent $event) { - //reorder list in case keys were dynamically removed $data = $event->getData(); + + // Reorder list in case keys were dynamically removed. if (is_array($data)) { $data = array_values($data); $event->setData($data); @@ -46,17 +32,11 @@ function (FormEvent $event) { ); } - /** - * {@inheritdoc} - */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['isSortable'] = (!empty($options['sortable'])); } - /** - * {@inheritdoc} - */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults( @@ -122,17 +102,11 @@ public function configureOptions(OptionsResolver $resolver) ); } - /** - * {@inheritdoc} - */ public function getBlockPrefix() { return 'dynamiclist'; } - /** - * @return string - */ public function getParent() { return CollectionType::class; diff --git a/app/bundles/CoreBundle/Form/Type/EntityLookupType.php b/app/bundles/CoreBundle/Form/Type/EntityLookupType.php index 53c48058914..8e69ed98633 100644 --- a/app/bundles/CoreBundle/Form/Type/EntityLookupType.php +++ b/app/bundles/CoreBundle/Form/Type/EntityLookupType.php @@ -1,14 +1,5 @@ flatten($segentIdsInFilter); } diff --git a/app/bundles/CoreBundle/Form/Validator/Constraints/FileEncoding.php b/app/bundles/CoreBundle/Form/Validator/Constraints/FileEncoding.php index 4a29cc41395..a1f4c9cd89f 100644 --- a/app/bundles/CoreBundle/Form/Validator/Constraints/FileEncoding.php +++ b/app/bundles/CoreBundle/Form/Validator/Constraints/FileEncoding.php @@ -1,14 +1,5 @@ parse($list); - } catch (FormatNotSupportedException $exception) { - continue; - } - } + return static::parseChoiceList( + self::parseListsWithParsers( + $list, + [ + new JsonListParser(), + new BarListParser(), + new ValueListParser(), + new ArrayListParser(), + ] + ) + ); + } - return static::parseChoiceList($list); + /** + * Same as parseList method above but it will return labels as keys. + * + * @param mixed $list + * + * @return mixed[] + */ + public static function parseListForChoices($list): array + { + return static::parseChoiceList( + self::parseListsWithParsers( + $list, + [ + new JsonListParser(), + new BarListParser(), + new ValueListParser(), + new ArrayListParser(), + ] + ), + true + ); } + /** + * @param mixed $list + * + * @return mixed[] + */ public static function parseBooleanList($list): array { - /** @var ListParserInterface[] $parsers */ - $parsers = [ - new JsonListParser(), - new BarListParser(), - new ValueListParser(), - ]; - - $listParser = null; - foreach ($parsers as $parser) { - try { - $list = $parser->parse($list); - } catch (FormatNotSupportedException $exception) { - continue; - } - } - - return static::parseChoiceList($list); + return static::parseChoiceList( + self::parseListsWithParsers( + $list, + [ + new JsonListParser(), + new BarListParser(), + new ValueListParser(), + ] + ) + ); } /** * @param $format * @param $choices * - * @return array|string + * @return mixed[]|string */ public static function formatList($format, $choices) { @@ -188,7 +193,7 @@ public static function formatList($format, $choices) } } - protected static function parseChoiceList(array $list) + protected static function parseChoiceList(array $list, bool $labelsAsKeys = false) { $choices = []; foreach ($list as $value => $label) { @@ -201,7 +206,8 @@ protected static function parseChoiceList(array $list) continue; } - $choices[trim(html_entity_decode($value, ENT_QUOTES))] = trim(html_entity_decode($label, ENT_QUOTES)); + $choices = self::appendChoice($choices, $label, $value, $labelsAsKeys); + continue; } @@ -218,9 +224,47 @@ protected static function parseChoiceList(array $list) continue; } - $choices[trim(html_entity_decode($value, ENT_QUOTES))] = trim(html_entity_decode($label, ENT_QUOTES)); + $choices = self::appendChoice($choices, $label, $value, $labelsAsKeys); } return $choices; } + + /** + * @param mixed[] $choices + * + * @return mixed[] + */ + private static function appendChoice(array $choices, string $label, string $value, bool $labelsAsKeys = false): array + { + $label = trim(html_entity_decode($label, ENT_QUOTES)); + $value = trim(html_entity_decode($value, ENT_QUOTES)); + + if ($labelsAsKeys) { + $choices[$label] = $value; + } else { + $choices[$value] = $label; + } + + return $choices; + } + + /** + * @param mixed $list + * @param ListParserInterface[] $parsers + * + * @return mixed[] + */ + private static function parseListsWithParsers($list, array $parsers): array + { + foreach ($parsers as $parser) { + try { + $list = $parser->parse($list); + } catch (FormatNotSupportedException $exception) { + continue; + } + } + + return $list; + } } diff --git a/app/bundles/CoreBundle/Helper/AppVersion.php b/app/bundles/CoreBundle/Helper/AppVersion.php index c4e167994d6..71dd51d19b8 100644 --- a/app/bundles/CoreBundle/Helper/AppVersion.php +++ b/app/bundles/CoreBundle/Helper/AppVersion.php @@ -1,14 +1,5 @@ cacheDir = $cacheDir; - $this->session = $session; - $this->configFile = $pathsHelper->getLocalConfigurationFile(); + $this->cacheDir = $cacheDir; + $this->session = $session; + $this->pathsHelper = $pathsHelper; + $this->kernel = $kernel; } /** @@ -59,6 +45,26 @@ public function refreshConfig(): void $this->clearApcuCache(); } + /** + * Run the bin/console cache:clear command. + */ + public function clearSymfonyCache(): int + { + $env = $this->kernel->getEnvironment(); + + $application = new Application($this->kernel); + $application->setAutoExit(false); + + $input = new ArrayInput([ + 'command' => 'cache:clear', + '--env' => $env, + ]); + + $output = new BufferedOutput(); + + return $application->run($input, $output); + } + /** * Clear cache related session items. */ @@ -79,7 +85,7 @@ private function clearConfigOpcache(): void return; } - opcache_invalidate($this->configFile, true); + opcache_invalidate($this->pathsHelper->getLocalConfigurationFile(), true); } private function clearOpcache(): void diff --git a/app/bundles/CoreBundle/Helper/CacheStorageHelper.php b/app/bundles/CoreBundle/Helper/CacheStorageHelper.php index 7e5523a1b0b..1288e44ec4e 100644 --- a/app/bundles/CoreBundle/Helper/CacheStorageHelper.php +++ b/app/bundles/CoreBundle/Helper/CacheStorageHelper.php @@ -1,14 +1,5 @@ kernel = $kernel; + } + + /** + * @param array $params + */ + public function runCommand(string $name, array $params = []): CommandResponse + { + $params = array_merge(['command' => $name], $params); + $application = new Application($this->kernel); + $application->setAutoExit(false); + + $input = new ArrayInput($params); + $output = new BufferedOutput(); + $statusCode = $application->run($input, $output); + $message = $output->fetch(); + + return new CommandResponse($statusCode, $message); + } +} diff --git a/app/bundles/CoreBundle/Helper/CommandResponse.php b/app/bundles/CoreBundle/Helper/CommandResponse.php new file mode 100644 index 00000000000..f0622fceee0 --- /dev/null +++ b/app/bundles/CoreBundle/Helper/CommandResponse.php @@ -0,0 +1,28 @@ +statusCode = $statusCode; + $this->message = $message; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } +} diff --git a/app/bundles/CoreBundle/Helper/ComposerHelper.php b/app/bundles/CoreBundle/Helper/ComposerHelper.php new file mode 100644 index 00000000000..d96d27c06b9 --- /dev/null +++ b/app/bundles/CoreBundle/Helper/ComposerHelper.php @@ -0,0 +1,173 @@ +kernel = $kernel; + $this->logger = $logger; + } + + /** + * Installs a package using its Packagist name. + * + * @param string $packageName The package name, e.g. mautic/example-plugin + * @param bool $dryRun Whether to dry-run the installation. Comes in handy during automated tests + * and to test whether an installation would succeed or not. + */ + public function install(string $packageName, bool $dryRun = false): ConsoleOutputModel + { + $input = [ + 'command' => 'require', + 'packages' => [$packageName], + ]; + + if (true === $dryRun) { + $input['--dry-run'] = null; + } + + return $this->runCommand($input); + } + + /** + * Removes a package using its Packagist name. + * + * @param string $packageName The package name, e.g. mautic/example-plugin + * @param bool $dryRun Whether to dry-run the removal. Comes in handy during automated tests + * and to test whether an removal would succeed or not. + */ + public function remove(string $packageName, bool $dryRun = false): ConsoleOutputModel + { + /** + * "composer remove package-name" also triggers an update of all other Mautic dependencies. + * By using the --no-update option first, we can work around that issue and only delete + * this specific package from the composer.json file. + */ + $input = [ + 'command' => 'remove', + 'packages' => [$packageName], + '--no-update' => null, + ]; + + if (true === $dryRun) { + $input['--dry-run'] = null; + } + + $firstOutput = $this->runCommand($input); + + if (0 === $firstOutput->exitCode) { + /** + * Triggering an update of the package we just removed from composer.json + * will remove it from composer.lock and actually delete the plugin folder + * as well. + */ + $input = [ + 'command' => 'update', + 'packages' => [$packageName], + ]; + + if (true === $dryRun) { + $input['--dry-run'] = null; + } + + $secondOutput = $this->runCommand($input); + + // Let's merge the output so that we return all the output we have. + return new ConsoleOutputModel( + $secondOutput->exitCode, + $firstOutput->output."\n".$secondOutput->output + ); + } + + return $firstOutput; + } + + /** + * Checks if the given Composer package is installed. + * + * @param string $packageName The package name, e.g. mautic/exmple-plugin + */ + public function isInstalled(string $packageName): bool + { + return \Composer\InstalledVersions::isInstalled($packageName); + } + + /** + * Returns a list of installed Composer packages that are of type mautic-plugin. + * + * @return string[] + */ + public function getMauticPluginPackages(): array + { + return \Composer\InstalledVersions::getInstalledPackagesByType('mautic-plugin'); + } + + /** + * Updates one or multiple Composer packages. + */ + public function update(?string $packageName = null, bool $dryRun = false): ConsoleOutputModel + { + $input = [ + 'command' => 'update', + ]; + + if (!empty($packageName)) { + $input['packages'] = [$packageName]; + } + + if (true === $dryRun) { + $input['--dry-run'] = null; + } + + return $this->runCommand($input); + } + + /** + * @param array $input + */ + private function runCommand(array $input): ConsoleOutputModel + { + $arrayInput = new ArrayInput(array_merge( + $input, [ + '--no-interaction', + '--working-dir' => $this->kernel->getProjectDir(), + ])); + + $application = new Application(); + // We don't want our script to stop after running a Composer command + $application->setAutoExit(false); + + $this->logger->info('Running Composer command: '.$arrayInput->__toString()); + + $output = new BufferedOutput(); + $exitCode = 1; + + try { + $exitCode = $application->run($arrayInput, $output); + } catch (\Exception $e) { + $output->writeln('Exception while running Composer command: '.$e->getMessage()); + $this->logger->error('Exception while running Composer command: '.$e->getMessage()); + } + + $this->logger->info('Composer command output: '.$output->fetch()); + + return new ConsoleOutputModel($exitCode, $output->fetch()); + } +} diff --git a/app/bundles/CoreBundle/Helper/CookieHelper.php b/app/bundles/CoreBundle/Helper/CookieHelper.php index 284b6ba10e7..e457980ff61 100644 --- a/app/bundles/CoreBundle/Helper/CookieHelper.php +++ b/app/bundles/CoreBundle/Helper/CookieHelper.php @@ -1,14 +1,5 @@ datetime) { - $utc = ('UTC' == $this->timezone) ? $this->datetime : $this->datetime->setTimezone($this->utc); + $dateTime = clone $this->datetime; + $utc = ('UTC' == $this->timezone) ? $dateTime : $dateTime->setTimezone($this->utc); if (empty($format)) { $format = $this->format; } diff --git a/app/bundles/CoreBundle/Helper/EmojiHelper.php b/app/bundles/CoreBundle/Helper/EmojiHelper.php index 12fd69b0960..75416834728 100644 --- a/app/bundles/CoreBundle/Helper/EmojiHelper.php +++ b/app/bundles/CoreBundle/Helper/EmojiHelper.php @@ -1,14 +1,5 @@ - * - * @version 2.0 - * - * @link https://github.com/neitanod/forceutf8 - * - * @example https://github.com/neitanod/forceutf8 - * - * @license Revised BSD - */ - namespace Mautic\CoreBundle\Helper; class UTF8Helper diff --git a/app/bundles/CoreBundle/Helper/Update/Exception/CouldNotFetchLatestVersionException.php b/app/bundles/CoreBundle/Helper/Update/Exception/CouldNotFetchLatestVersionException.php index 8fbbe6eeb3f..005e294dca3 100644 --- a/app/bundles/CoreBundle/Helper/Update/Exception/CouldNotFetchLatestVersionException.php +++ b/app/bundles/CoreBundle/Helper/Update/Exception/CouldNotFetchLatestVersionException.php @@ -1,14 +1,5 @@ > + */ + protected static $permissions = [ + 'file' => [ + 'public' => 0666, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0777, + 'private' => 0700, + ], + ]; + + public function __construct(string $root) + { + parent::__construct($root, LOCK_EX, self::DISALLOW_LINKS, self::$permissions); + } +} diff --git a/app/bundles/CoreBundle/Session/Storage/Handler/RedisSentinelSessionHandler.php b/app/bundles/CoreBundle/Session/Storage/Handler/RedisSentinelSessionHandler.php index 9d54db78117..2be4b6f7946 100644 --- a/app/bundles/CoreBundle/Session/Storage/Handler/RedisSentinelSessionHandler.php +++ b/app/bundles/CoreBundle/Session/Storage/Handler/RedisSentinelSessionHandler.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2020 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Session\Storage\Handler; use Mautic\CoreBundle\Helper\CoreParametersHelper; diff --git a/app/bundles/CoreBundle/Templating/Engine/PhpEngine.php b/app/bundles/CoreBundle/Templating/Engine/PhpEngine.php index d912aed368c..0e13496d68e 100644 --- a/app/bundles/CoreBundle/Templating/Engine/PhpEngine.php +++ b/app/bundles/CoreBundle/Templating/Engine/PhpEngine.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Mautic\CoreBundle\Templating; use Symfony\Bundle\FrameworkBundle\Templating\TemplateNameParser as BaseTemplateNameParser; diff --git a/app/bundles/CoreBundle/Templating/TemplateReference.php b/app/bundles/CoreBundle/Templating/TemplateReference.php index 13f159a5ccd..ab0233fe3c9 100644 --- a/app/bundles/CoreBundle/Templating/TemplateReference.php +++ b/app/bundles/CoreBundle/Templating/TemplateReference.php @@ -1,14 +1,5 @@ true, 'api_enable_basic_auth' => true, 'create_custom_field_in_background' => false, + 'mailer_from_name' => 'Mautic', ]; protected function setUp(): void @@ -151,18 +154,19 @@ protected function createAjaxHeaders(): array } /** - * @param $name - * - * @return string + * @return string Command's output * * @throws \Exception + * + * @deprecated use testSymfonyCommand() instead */ - protected function runCommand($name, array $params = [], Command $command = null) + protected function runCommand(string $name, array $params = [], Command $command = null, int $expectedStatusCode = 0): string { $params = array_merge(['command' => $name], $params); $kernel = self::$container->get('kernel'); $application = new Application($kernel); $application->setAutoExit(false); + $application->setCatchExceptions(false); if ($command) { if ($command instanceof ContainerAwareCommand) { @@ -173,9 +177,11 @@ protected function runCommand($name, array $params = [], Command $command = null $application->add($command); } - $input = new ArrayInput($params); - $output = new BufferedOutput(); - $application->run($input, $output); + $input = new ArrayInput($params); + $output = new BufferedOutput(); + $statusCode = $application->run($input, $output); + + Assert::assertSame($expectedStatusCode, $statusCode); return $output->fetch(); } @@ -197,4 +203,24 @@ protected function loginUser(string $username): void $cookie = new Cookie($session->getName(), $session->getId()); $this->client->getCookieJar()->set($cookie); } + + /** + * @param array $params + */ + protected function testSymfonyCommand(string $name, array $params = [], Command $command = null): CommandTester + { + $kernel = self::$container->get('kernel'); + $application = new Application($kernel); + + if ($command) { + // Register the command + $application->add($command); + } + + $command = $application->find($name); + $commandTester = new CommandTester($command); + $commandTester->execute($params); + + return $commandTester; + } } diff --git a/app/bundles/CoreBundle/Test/EventListener/CoreSubscriberTest.php b/app/bundles/CoreBundle/Test/EventListener/CoreSubscriberTest.php index c9771a94407..8d9494ad8c7 100644 --- a/app/bundles/CoreBundle/Test/EventListener/CoreSubscriberTest.php +++ b/app/bundles/CoreBundle/Test/EventListener/CoreSubscriberTest.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2020 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Test\EventListener; use Mautic\CoreBundle\Controller\MauticController; diff --git a/app/bundles/CoreBundle/Test/EventListener/MaintenanceSubscriberTest.php b/app/bundles/CoreBundle/Test/EventListener/MaintenanceSubscriberTest.php index d40041a1361..9589e7e2caa 100644 --- a/app/bundles/CoreBundle/Test/EventListener/MaintenanceSubscriberTest.php +++ b/app/bundles/CoreBundle/Test/EventListener/MaintenanceSubscriberTest.php @@ -1,14 +1,5 @@ addTestSuite($testClass); + $result = $testSuite->run(); + + if (!$result->wasSuccessful()) { + $failures = array_map(function (TestFailure $testFailure) { + return $testFailure->getExceptionAsString(); + }, array_merge($result->failures(), $result->errors())); + + exit(sprintf('The previous test was: "%s". Your test errored with: %s', $test, implode(PHP_EOL, $failures))); + } + } +} diff --git a/app/bundles/CoreBundle/Test/Listeners/CleanupListener.php b/app/bundles/CoreBundle/Test/Listeners/CleanupListener.php index eadd842395c..30cb82cc74f 100644 --- a/app/bundles/CoreBundle/Test/Listeners/CleanupListener.php +++ b/app/bundles/CoreBundle/Test/Listeners/CleanupListener.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2021 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link http://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Test\Listeners; use Closure; diff --git a/app/bundles/CoreBundle/Test/MauticMysqlTestCase.php b/app/bundles/CoreBundle/Test/MauticMysqlTestCase.php index 2f880ef121a..7b78a2fc29c 100644 --- a/app/bundles/CoreBundle/Test/MauticMysqlTestCase.php +++ b/app/bundles/CoreBundle/Test/MauticMysqlTestCase.php @@ -8,6 +8,8 @@ use Mautic\InstallBundle\InstallFixtures\ORM\RoleData; use Mautic\UserBundle\DataFixtures\ORM\LoadRoleData; use Mautic\UserBundle\DataFixtures\ORM\LoadUserData; +use Symfony\Bundle\FrameworkBundle\Client; +use Symfony\Component\Process\Process; abstract class MauticMysqlTestCase extends AbstractMauticTestCase { @@ -25,6 +27,18 @@ abstract class MauticMysqlTestCase extends AbstractMauticTestCase */ protected $useCleanupRollback = true; + /** + * @param array $data + */ + public function __construct(?string $name = null, array $data = [], $dataName = '') + { + parent::__construct($name, $data, $dataName); + + $this->configParams += [ + 'db_driver' => 'pdo_mysql', + ]; + } + /** * @throws Exception */ @@ -91,6 +105,20 @@ protected function resetAutoincrement(array $tables): void } } + protected function createAnotherClient(string $username = 'admin', string $password = 'mautic'): Client + { + // turn off rollback cleanup as this client creates a separate DB connection + $this->useCleanupRollback = false; + + return self::createClient( + $this->clientOptions, + [ + 'PHP_AUTH_USER' => $username, + 'PHP_AUTH_PW' => $password, + ] + ); + } + /** * Warning: To perform Truncate on tables with foreign keys we have to turn off the foreign keys temporarily. * This may lead to corrupted data. Make sure you know what you are doing. @@ -114,13 +142,22 @@ protected function truncateTables(string ...$tables): void private function applySqlFromFile($file) { $connection = $this->connection; - $password = ($connection->getPassword()) ? " -p{$connection->getPassword()}" : ''; - $command = "mysql -h{$connection->getHost()} -P{$connection->getPort()} -u{$connection->getUsername()}$password {$connection->getDatabase()} < {$file} 2>&1 | grep -v \"Using a password\" || true"; - - $lastLine = system($command, $status); - - if (0 !== $status) { - throw new Exception($command.' failed with status code '.$status.' and last line of "'.$lastLine.'"'); + $command = 'mysql -h"${:db_host}" -P"${:db_port}" -u"${:db_user}" "${:db_name}" < "${:db_backup_file}"'; + $envVars = [ + 'MYSQL_PWD' => $connection->getPassword(), + 'db_host' => $connection->getHost(), + 'db_port' => $connection->getPort(), + 'db_user' => $connection->getUsername(), + 'db_name' => $connection->getDatabase(), + 'db_backup_file' => $file, + ]; + + $process = Process::fromShellCommandline($command); + $process->run(null, $envVars); + + // executes after the command finishes + if (!$process->isSuccessful()) { + throw new Exception($command.' failed with status code '.$process->getExitCode().' and last line of "'.$process->getErrorOutput().'"'); } } @@ -131,7 +168,7 @@ private function applySqlFromFile($file) */ private function prepareDatabase() { - if (!function_exists('system')) { + if (!function_exists('proc_open')) { $this->installDatabase(); return; @@ -164,27 +201,9 @@ private function installDatabase() */ private function createDatabase() { - $this->runCommand( - 'doctrine:database:drop', - [ - '--env' => 'test', - '--force' => true, - ] - ); - - $this->runCommand( - 'doctrine:database:create', - [ - '--env' => 'test', - ] - ); - - $this->runCommand( - 'doctrine:schema:create', - [ - '--env' => 'test', - ] - ); + $this->runCommand('doctrine:database:drop', ['--if-exists' => true, '--force' => true]); + $this->runCommand('doctrine:database:create'); + $this->runCommand('doctrine:schema:create'); } /** @@ -192,22 +211,27 @@ private function createDatabase() */ private function dumpToFile(string $sqlDumpFile): void { - $password = ($this->connection->getPassword()) ? " -p{$this->connection->getPassword()}" : ''; - $command = "mysqldump --add-drop-table --opt -h{$this->connection->getHost()} -P{$this->connection->getPort()} -u{$this->connection->getUsername()}$password {$this->connection->getDatabase()} > {$sqlDumpFile} 2>&1 | grep -v \"Using a password\" || true"; - - $lastLine = system($command, $status); - if (0 !== $status) { - throw new Exception($command.' failed with status code '.$status.' and last line of "'.$lastLine.'"'); - } - - $f = fopen($sqlDumpFile, 'r'); - $firstLine = fgets($f); - if (false !== strpos($firstLine, 'Using a password')) { - $file = file($sqlDumpFile); - unset($file[0]); - file_put_contents($sqlDumpFile, $file); + $connection = $this->connection; + $command = 'mysqldump --opt -h"${:db_host}" -P"${:db_port}" -u"${:db_user}" "${:db_name}" > "${:db_backup_file}"'; + $envVars = [ + 'MYSQL_PWD' => $connection->getPassword(), + 'db_host' => $connection->getHost(), + 'db_port' => $connection->getPort(), + 'db_user' => $connection->getUsername(), + 'db_name' => $connection->getDatabase(), + 'db_backup_file' => $sqlDumpFile, + ]; + + $process = Process::fromShellCommandline($command); + $process->run(null, $envVars); + + // executes after the command finishes + if (!$process->isSuccessful()) { + if (file_exists($sqlDumpFile)) { + unlink($sqlDumpFile); + } + throw new Exception($command.' failed with status code '.$process->getExitCode().' and last line of "'.$process->getErrorOutput().'"'); } - fclose($f); } /** diff --git a/app/bundles/CoreBundle/Test/Service/FlashBagTest.php b/app/bundles/CoreBundle/Test/Service/FlashBagTest.php index a64fa066e9e..d416ec397c7 100644 --- a/app/bundles/CoreBundle/Test/Service/FlashBagTest.php +++ b/app/bundles/CoreBundle/Test/Service/FlashBagTest.php @@ -1,14 +1,5 @@ getMockBuilder(Translator::class) - ->disableOriginalConstructor() - ->getMock(); - $translator->expects($this->any()) - ->method('hasId') + $translator = $this->createMock(Translator::class); + $translator->method('hasId') ->will($this->returnValue(false)); return $translator; } /** - * @return EntityManager + * @return MockObject&EntityManager */ protected function getEntityManagerMock() { - $entityManager = $this - ->getMockBuilder(EntityManager::class) - ->disableOriginalConstructor() - ->getMock(); - - return $entityManager; + return $this->createMock(EntityManager::class); } /** - * @return PathsHelper + * @return MockObject&PathsHelper */ protected function getPathsHelperMock() { - $pathsHelper = $this->getMockBuilder(PathsHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - return $pathsHelper; + return $this->createMock(PathsHelper::class); } /** - * @return CoreParametersHelper + * @return MockObject&CoreParametersHelper */ protected function getCoreParametersHelperMock() { - $paramHelper = $this->getMockBuilder(CoreParametersHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - return $paramHelper; + return $this->createMock(CoreParametersHelper::class); } /** - * @return BundleHelper + * @return MockObject&BundleHelper */ protected function getBundleHelperMock() { - $bundleHelper = $this->getMockBuilder(BundleHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - return $bundleHelper; + return $this->createMock(BundleHelper::class); } /** - * @return IpLookupHelper + * @return MockObject&IpLookupHelper */ protected function getIpLookupHelperMock() { - return $this->getMockBuilder(IpLookupHelper::class) - ->disableOriginalConstructor() - ->getMock(); + return $this->createMock(IpLookupHelper::class); } /** - * @return AuditLogModel + * @return MockObject&AuditLogModel */ protected function getAuditLogModelMock() { - return $this->getMockBuilder(AuditLogModel::class) - ->disableOriginalConstructor() - ->getMock(); + return $this->createMock(AuditLogModel::class); } } diff --git a/app/bundles/CoreBundle/Tests/Form/RequestTraitTest.php b/app/bundles/CoreBundle/Tests/Form/RequestTraitTest.php index 9412c008266..7d230f484e1 100644 --- a/app/bundles/CoreBundle/Tests/Form/RequestTraitTest.php +++ b/app/bundles/CoreBundle/Tests/Form/RequestTraitTest.php @@ -1,14 +1,5 @@ assertSame($expectedValues, $params); } + + /** + * @dataProvider boolProvider + * + * @param string|int|bool|null $value + */ + public function testCleanFieldsBoolean(?bool $expected, $value): void + { + $fieldData = ['boolVal' => $value]; + $leadField = [ + 'alias' => 'boolVal', + 'type' => 'boolean', + ]; + + $this->cleanFields($fieldData, $leadField); + $this->assertSame(['boolVal' => $expected], $fieldData); + } + + /** + * @return iterable> + */ + public function boolProvider(): iterable + { + yield [true, '1']; + yield [true, 1]; + yield [true, true]; + yield [true, 'Y']; + yield [true, 'y']; + yield [true, 'yES']; + yield [true, 'T']; + yield [true, 't']; + yield [true, 'true']; + yield [null, null]; + yield [false, '0']; + yield [false, 0]; + yield [false, false]; + yield [false, 'N']; + yield [false, 'n']; + yield [false, 'No']; + yield [false, 'F']; + yield [false, 'f']; + yield [false, 'false']; + } + + public function testCleanFieldsDateTime(): void + { + $fieldData = ['fieldDateTime' => '10/10/2022 05:10:25']; + $leadField = [ + 'alias' => 'fieldDateTime', + 'type' => 'datetime', + ]; + $expectedValues = ['fieldDateTime' => '2022-10-10 05:10:25']; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } + + public function testCleanFieldsDate(): void + { + $fieldData = ['fieldDate' => '10/10/2022']; + $leadField = [ + 'alias' => 'fieldDate', + 'type' => 'date', + ]; + $expectedValues = ['fieldDate' => '2022-10-10']; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } + + public function testCleanFieldsTime(): void + { + $fieldData = ['fieldTime' => '05:20:10']; + $leadField = [ + 'alias' => 'fieldTime', + 'type' => 'time', + ]; + $expectedValues = ['fieldTime' => '05:20:10']; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } + + public function testCleanFieldsWrongDateTime(): void + { + $fieldData = ['fieldTime' => 'string']; + $leadField = [ + 'alias' => 'fieldTime', + 'type' => 'time', + ]; + $expectedValues = []; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } + + public function testCleanFieldsMultiSelectArray(): void + { + $fieldData = ['fieldMultiSelect' => ['o1', 'o2', 'o3']]; + $leadField = [ + 'alias' => 'fieldMultiSelect', + 'type' => 'multiselect', + ]; + $expectedValues = ['fieldMultiSelect' => ['o1', 'o2', 'o3']]; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } + + public function testCleanFieldsMultiSelectString(): void + { + $fieldData = ['fieldMultiSelect' => 'o1|o2|o3']; + $leadField = [ + 'alias' => 'fieldMultiSelect', + 'type' => 'multiselect', + ]; + $expectedValues = ['fieldMultiSelect' => ['o1', 'o2', 'o3']]; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } + + public function testCleanFieldsNumber(): void + { + $fieldData = ['fieldFloat' => '3.2']; + $leadField = [ + 'alias' => 'fieldFloat', + 'type' => 'number', + ]; + $expectedValues = ['fieldFloat' => 3.2]; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } + + public function testCleanFieldsEmail(): void + { + $fieldData = ['fieldEmail' => 'email@domain.com']; + $leadField = [ + 'alias' => 'fieldEmail', + 'type' => 'email', + ]; + $expectedValues = ['fieldEmail' => 'email@domain.com']; + $this->cleanFields($fieldData, $leadField); + $this->assertSame($expectedValues, $fieldData); + } } diff --git a/app/bundles/CoreBundle/Tests/Functional/Controller/FileControllerTest.php b/app/bundles/CoreBundle/Tests/Functional/Controller/FileControllerTest.php index 7a7347fd4be..5a4d756d512 100644 --- a/app/bundles/CoreBundle/Tests/Functional/Controller/FileControllerTest.php +++ b/app/bundles/CoreBundle/Tests/Functional/Controller/FileControllerTest.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2021 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link http://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Tests\Functional\Controller; use Mautic\CoreBundle\Test\MauticMysqlTestCase; @@ -68,8 +59,10 @@ private function getFixurePath(): string return realpath(dirname(__FILE__).'/../../Fixtures/').'/'; } - protected function beforeTearDown(): void + protected function tearDown(): void { + parent::tearDown(); + if ($this->uploadedFilePath && file_exists($this->uploadedFilePath)) { unlink($this->uploadedFilePath); } diff --git a/app/bundles/CoreBundle/Tests/Functional/Entity/CommonRepositoryTest.php b/app/bundles/CoreBundle/Tests/Functional/Entity/CommonRepositoryTest.php index fb359f2b26e..464a75f0e98 100644 --- a/app/bundles/CoreBundle/Tests/Functional/Entity/CommonRepositoryTest.php +++ b/app/bundles/CoreBundle/Tests/Functional/Entity/CommonRepositoryTest.php @@ -1,14 +1,5 @@ commandHelper = $this->getContainer()->get('mautic.helper.command'); + } + + public function testRunCommandWithParam(): void + { + $response = $this->commandHelper->runCommand('help', ['--version']); + Assert::assertSame(0, $response->getStatusCode()); + Assert::assertStringContainsString('(env: test, debug: false)', $response->getMessage()); + } + + public function testRunCommandWithoutParam(): void + { + $response = $this->commandHelper->runCommand('list'); + Assert::assertSame(0, $response->getStatusCode()); + Assert::assertStringContainsString('doctrine:database:create', $response->getMessage()); + } +} diff --git a/app/bundles/CoreBundle/Tests/Functional/Service/LocalFileAdapterServiceTest.php b/app/bundles/CoreBundle/Tests/Functional/Service/LocalFileAdapterServiceTest.php new file mode 100644 index 00000000000..5836806b485 --- /dev/null +++ b/app/bundles/CoreBundle/Tests/Functional/Service/LocalFileAdapterServiceTest.php @@ -0,0 +1,67 @@ +get('mautic.helper.paths'); + $folderPath = "{$pathsHelper->getImagePath()}/$this->folderName"; + + if (is_dir($folderPath)) { + rmdir($folderPath); + } + } + + public function testElfinderCreateFolderPermissions(): void + { + $elFinderLoader = new class(self::$container) extends ElFinderLoader { + public function __construct(ContainerInterface $container) + { + parent::__construct($container->get('fm_elfinder.configurator')); + } + + /** + * @return array + */ + public function load(Request $request) + { + $connector = new ElFinderConnector($this->bridge); + + return $connector->execute($request->query->all()); + } + }; + + self::$container->set('fm_elfinder.loader', $elFinderLoader); + + $this->folderName = (string) time(); + $this->loginUser('admin'); + $_SERVER['REQUEST_METHOD'] = Request::METHOD_POST; + $this->client->request( + Request::METHOD_POST, + "efconnect?cmd=mkdir&name=$this->folderName&target=fls1_Lw" + ); + $response = $this->client->getResponse(); + self::assertSame(200, $response->getStatusCode()); + /** @var PathsHelper $pathsHelper */ + $pathsHelper = self::$container->get('mautic.helper.paths'); + $folderPath = "{$pathsHelper->getImagePath()}/$this->folderName"; + self::assertDirectoryExists($folderPath); + self::assertSame('777', substr(sprintf('%o', fileperms($folderPath)), -3)); + } +} diff --git a/app/bundles/CoreBundle/Tests/Traits/ControllerTrait.php b/app/bundles/CoreBundle/Tests/Traits/ControllerTrait.php new file mode 100644 index 00000000000..c191a71c267 --- /dev/null +++ b/app/bundles/CoreBundle/Tests/Traits/ControllerTrait.php @@ -0,0 +1,76 @@ +client->request('GET', '/s/'.$urlAlias); + $clientResponse = $this->client->getResponse(); + $responseContent = $clientResponse->getContent(); + PageControllerTest::assertTrue($clientResponse->isOk()); + + PageControllerTest::assertStringContainsString( + 'col-'.$routeAlias.'-dateAdded', + $responseContent, + 'The return must contain the created at date column' + ); + PageControllerTest::assertStringContainsString( + 'col-'.$routeAlias.'-'.$column, + $responseContent, + 'The return must contain the modified date column' + ); + + PageControllerTest::assertEquals( + 1, + $crawler->filterXPath( + "//th[contains(@class,'col-".$routeAlias.'-'.$column."')]//i[contains(@class, 'fa-sort-amount-desc')]" + )->count(), + 'The order must be desc' + ); + + $crawler = $this->client->request( + 'GET', + '/s/'.$urlAlias.'?tmpl=list&name='.$routeAlias.'&orderby='.$tableAlias.$column + ); + PageControllerTest::assertEquals( + 1, + $crawler->filterXPath( + "//th[contains(@class,'col-".$routeAlias.'-'.$column."')]//i[contains(@class, 'fa-sort-amount-asc')]" + )->count(), + 'The order must be asc' + ); + + $crawler = $this->client->request( + 'GET', + '/s/'.$urlAlias.'?tmpl=list&name='.$routeAlias.'&orderby='.$tableAlias.$column2 + ); + PageControllerTest::assertEquals( + 1, + $crawler->filterXPath( + "//th[contains(@class,'col-".$routeAlias.'-'.$column2."')]//i[contains(@class, 'fa-sort-amount-asc')]" + )->count(), + 'The order must be asc' + ); + + $crawler = $this->client->request( + 'GET', + '/s/'.$urlAlias.'?tmpl=list&name='.$routeAlias.'&orderby='.$tableAlias.$column2 + ); + PageControllerTest::assertEquals( + 1, + $crawler->filterXPath( + "//th[contains(@class,'col-".$routeAlias.'-'.$column2."')]//i[contains(@class, 'fa-sort-amount-desc')]" + )->count(), + 'The order must be desc' + ); + } +} diff --git a/app/bundles/CoreBundle/Tests/Unit/Command/ModeratedCommandTest.php b/app/bundles/CoreBundle/Tests/Unit/Command/ModeratedCommandTest.php index 8e6a3d7a40b..050009e1bb4 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Command/ModeratedCommandTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Command/ModeratedCommandTest.php @@ -1,14 +1,5 @@ [ 'main' => [ diff --git a/app/bundles/CoreBundle/Tests/Unit/DependencyInjection/EnvProcessor/IntNullableProcessorTest.php b/app/bundles/CoreBundle/Tests/Unit/DependencyInjection/EnvProcessor/IntNullableProcessorTest.php index 7d8eb7cc1f1..da19ad26f82 100644 --- a/app/bundles/CoreBundle/Tests/Unit/DependencyInjection/EnvProcessor/IntNullableProcessorTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/DependencyInjection/EnvProcessor/IntNullableProcessorTest.php @@ -1,14 +1,5 @@ format()); + Assert::assertSame($expected, (string) $fulltextKeyword); + } + + /** + * @return iterable> + */ + public function dataDefault(): iterable + { + yield ['some word', '(+some* +word*) >"some word"']; + yield ['another', '(+another*) >"another"']; + yield ['s', '(+s*) >"s"']; + yield ['', '']; + } + + /** + * @dataProvider dataInflectingEnabled + */ + public function testInflectingEnabled(string $value, string $expected): void + { + $fulltextKeyword = new FulltextKeyword($value, true, true, true); + + Assert::assertSame($expected, $fulltextKeyword->format()); + Assert::assertSame($expected, (string) $fulltextKeyword); + } + + /** + * @return iterable> + */ + public function dataInflectingEnabled(): iterable + { + yield ['some word', '(+(some* "some word"']; + yield ['another', '(+(another* "another"']; + yield ['s', '(+s*) >"s"']; + yield ['', '']; + } + + /** + * @dataProvider dataWordSearchDisabled + */ + public function testWordSearchDisabled(string $value, string $expected): void + { + $fulltextKeyword = new FulltextKeyword($value, true, false); + + Assert::assertSame($expected, $fulltextKeyword->format()); + Assert::assertSame($expected, (string) $fulltextKeyword); + } + + /** + * @return iterable> + */ + public function dataWordSearchDisabled(): iterable + { + yield ['some word', '"some word"']; + yield ['another', '"another"']; + yield ['s', '"s"']; + yield ['', '']; + } + + /** + * @dataProvider dataBooleanModeDisabled + */ + public function testBooleanModeDisabled(string $value, string $expected): void + { + $fulltextKeyword = new FulltextKeyword($value, false); + + Assert::assertSame($expected, $fulltextKeyword->format()); + Assert::assertSame($expected, (string) $fulltextKeyword); + } + + /** + * @return iterable> + */ + public function dataBooleanModeDisabled(): iterable + { + yield ['some word', 'some word']; + yield ['another', 'another']; + yield ['s', 's']; + yield ['', '']; + } +} diff --git a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/ClassMetadataBuilderTest.php b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/ClassMetadataBuilderTest.php index d9bef7f14f1..2c33f93e911 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/ClassMetadataBuilderTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/ClassMetadataBuilderTest.php @@ -1,14 +1,5 @@ classMetadataBuilder->addNullableField('columnName', Types::STRING, 'column_name'); } + + public function testaddIndexWithOptions(): void + { + $columns = [ + 'column_1', + 'column_2', + ]; + + $options = [ + 'lengths' => [ + 0 => 128, + ], + ]; + + $index_name = 'index'; + + $data = $this->classMetadataBuilder->addIndexWithOptions($columns, $index_name, $options); + + $this->assertEquals($columns, $data->getClassMetadata()->table['indexes'][$index_name]['columns']); + $this->assertEquals($options, $data->getClassMetadata()->table['indexes'][$index_name]['options']); + } } diff --git a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnTest.php b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnTest.php index ea930bb22f0..c5bdec74e07 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnTest.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2018 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link http://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Tests\Unit\Doctrine\Mapping\GeneratedColumn; use Mautic\CoreBundle\Doctrine\GeneratedColumn\GeneratedColumn; diff --git a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnsTest.php b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnsTest.php index 30631898ea6..a21a6636c2b 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnsTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Mapping/GeneratedColumn/GeneratedColumnsTest.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2018 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link http://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Tests\Unit\Doctrine\Mapping\GeneratedColumn; use Mautic\CoreBundle\Doctrine\GeneratedColumn\GeneratedColumn; diff --git a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/GeneratedColumnsProviderTest.php b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/GeneratedColumnsProviderTest.php index 3e30a1e165b..f7576fb19fd 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/GeneratedColumnsProviderTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/GeneratedColumnsProviderTest.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2018 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link https://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Tests\Unit\Doctrine\Provider; use Mautic\CoreBundle\CoreEvents; diff --git a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/VersionProviderTest.php b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/VersionProviderTest.php index eac7874f036..dec31d5ccd0 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/VersionProviderTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Doctrine/Provider/VersionProviderTest.php @@ -2,15 +2,6 @@ declare(strict_types=1); -/* - * @copyright 2018 Mautic Contributors. All rights reserved - * @author Mautic - * - * @link http://mautic.org - * - * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html - */ - namespace Mautic\CoreBundle\Tests\Unit\Doctrine\Provider; use Doctrine\DBAL\Connection; diff --git a/app/bundles/CoreBundle/Tests/Unit/Entity/CommonRepositoryTest.php b/app/bundles/CoreBundle/Tests/Unit/Entity/CommonRepositoryTest.php index a9005db63f0..e6e1bdadfa4 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Entity/CommonRepositoryTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Entity/CommonRepositoryTest.php @@ -1,14 +1,5 @@ + */ + private $formBuilder; + + /** + * @var MockObject&FormEvent + */ + private $formEvent; + + /** + * @var MockObject&FormInterface + */ + private $form; + + /** + * @var MockObject (use DynamicContentEntityTrait) + */ + private $entity; + + /** + * @var MockObject (use DynamicContentTrait) + */ + private $trait; + + protected function setUp(): void + { + parent::setUp(); + + $this->formBuilder = $this->createMock(FormBuilderInterface::class); + $this->formEvent = $this->createMock(FormEvent::class); + $this->form = $this->createMock(FormInterface::class); + $this->entity = $this->getMockForTrait(DynamicContentEntityTrait::class); + $this->trait = $this->getMockForTrait(DynamicContentTrait::class); + } + + /** + * There is a problem when a user just grags&drop the Dynamic Content slot + * without configuring it. New email won't save with no error. We must ensure + * each dynamic content slot has its full structure. + */ + public function testAddDynamicContentFieldWithDecWithoutFiltersAndContent(): void + { + $this->formBuilder->expects($this->once()) + ->method('addEventListener') + ->with( + FormEvents::PRE_SUBMIT, + $this->callback(function ($formModifier) { + $inputData = [ + 'dynamicContent' => [ + [ + 'content' => '', + ], + ], + ]; + + $outputData = [ + 'dynamicContent' => [ + [ + 'content' => '', + 'filters' => [ + [ + 'content' => null, + 'filters' => [ + [ + 'glue' => null, + 'field' => null, + 'object' => null, + 'type' => null, + 'operator' => null, + 'display' => null, + 'filter' => null, + ], + ], + ], + ], + ], + ], + ]; + + $this->formEvent->expects($this->once()) + ->method('getForm') + ->willReturn($this->form); + + $this->form->expects($this->once()) + ->method('getData') + ->willReturn($this->entity); + + $this->formEvent->expects($this->once()) + ->method('getData') + ->willReturn($inputData); + + $this->formEvent->expects($this->once()) + ->method('setData') + ->with($outputData); + + $formModifier($this->formEvent); + + return true; + }) + ); + + $this->invokeMethod($this->trait, 'addDynamicContentField', [$this->formBuilder]); + } + + /** + * @param mixed[] $args + * + * @return mixed + */ + private function invokeMethod(object $object, string $methodName, array $args = []) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + return $method->invokeArgs($object, $args); + } +} diff --git a/app/bundles/CoreBundle/Tests/Unit/Form/Type/DynamicListTypeTest.php b/app/bundles/CoreBundle/Tests/Unit/Form/Type/DynamicListTypeTest.php new file mode 100644 index 00000000000..2a7d65b282e --- /dev/null +++ b/app/bundles/CoreBundle/Tests/Unit/Form/Type/DynamicListTypeTest.php @@ -0,0 +1,84 @@ + + */ + private $formBuilder; + + /** + * @var DynamicListType + */ + private $form; + + protected function setUp(): void + { + parent::setUp(); + + $this->formBuilder = $this->createMock(FormBuilderInterface::class); + $this->form = new DynamicListType(); + } + + public function testBuildFormWhenDataIsNull(): void + { + $this->formBuilder->expects($this->once()) + ->method('addEventListener') + ->with( + FormEvents::PRE_SUBMIT, + $this->callback(function ($formModifier) { + $formEvent = $this->createMock(FormEvent::class); + + $formEvent->expects($this->once()) + ->method('getData') + ->willReturn(null); + + $formEvent->expects($this->never()) + ->method('setData'); + + $formModifier($formEvent); + + return true; + }) + ); + + $this->form->buildForm($this->formBuilder, []); + } + + public function testBuildFormWhenDataIsArray(): void + { + $this->formBuilder->expects($this->once()) + ->method('addEventListener') + ->with( + FormEvents::PRE_SUBMIT, + $this->callback(function ($formModifier) { + $formEvent = $this->createMock(FormEvent::class); + $data = [['content' => 'dynamic slot content']]; + + $formEvent->expects($this->once()) + ->method('getData') + ->willReturn($data); + + $formEvent->expects($this->once()) + ->method('setData') + ->with($data); + + $formModifier($formEvent); + + return true; + }) + ); + + $this->form->buildForm($this->formBuilder, []); + } +} diff --git a/app/bundles/CoreBundle/Tests/Unit/Form/Validator/Constraints/CircularDependencyValidatorTest.php b/app/bundles/CoreBundle/Tests/Unit/Form/Validator/Constraints/CircularDependencyValidatorTest.php index 60fcc654af5..4c2156bb3d7 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Form/Validator/Constraints/CircularDependencyValidatorTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Form/Validator/Constraints/CircularDependencyValidatorTest.php @@ -84,7 +84,7 @@ private function configureValidator($expectedMessage, $currentSegmentId) 'field' => 'leadlist', 'object' => 'lead', 'type' => 'leadlist', - 'filter' => [2], + 'filter' => [2], // Keeping filter in the root to test also for BC segments. 'display' => null, 'operator' => 'in', ], @@ -92,25 +92,25 @@ private function configureValidator($expectedMessage, $currentSegmentId) $filters2 = [ [ - 'glue' => 'and', - 'field' => 'leadlist', - 'object' => 'lead', - 'type' => 'leadlist', - 'filter' => [1], - 'display' => null, - 'operator' => 'in', + 'glue' => 'and', + 'field' => 'leadlist', + 'object' => 'lead', + 'type' => 'leadlist', + 'properties' => ['filter' => [1]], + 'display' => null, + 'operator' => 'in', ], ]; $filters3 = [ [ - 'glue' => 'and', - 'field' => 'first_name', - 'object' => 'lead', - 'type' => 'text', - 'filter' => 'John', - 'display' => null, - 'operator' => '=', + 'glue' => 'and', + 'field' => 'first_name', + 'object' => 'lead', + 'type' => 'text', + 'properties' => ['filter' => 'John'], + 'display' => null, + 'operator' => '=', ], ]; @@ -195,7 +195,7 @@ public function validateDataProvider() 'field' => 'leadlist', 'object' => 'lead', 'type' => 'leadlist', - 'filter' => [1], + 'filter' => [1], // Keeping filter in the root to test also for BC segments. 'display' => null, 'operator' => 'in', ], @@ -207,13 +207,13 @@ public function validateDataProvider() 1, // current segment id [ [ - 'glue' => 'and', - 'field' => 'leadlist', - 'object' => 'lead', - 'type' => 'leadlist', - 'filter' => [2], - 'display' => null, - 'operator' => 'in', + 'glue' => 'and', + 'field' => 'leadlist', + 'object' => 'lead', + 'type' => 'leadlist', + 'properties' => ['filter' => [2]], + 'display' => null, + 'operator' => 'in', ], ], ], @@ -224,13 +224,13 @@ public function validateDataProvider() 1, // current segment id [ [ - 'glue' => 'and', - 'field' => 'leadlist', - 'object' => 'lead', - 'type' => 'leadlist', - 'filter' => [3], - 'display' => null, - 'operator' => 'in', + 'glue' => 'and', + 'field' => 'leadlist', + 'object' => 'lead', + 'type' => 'leadlist', + 'properties' => ['filter' => [3]], + 'display' => null, + 'operator' => 'in', ], ], ], @@ -244,7 +244,7 @@ public function validateDataProvider() 'field' => 'first_name', 'object' => 'lead', 'type' => 'text', - 'filter' => 'Doe', + 'filter' => 'Doe', // Keeping filter in the root to test also for BC segments. 'display' => null, 'operator' => '=', ], @@ -256,22 +256,22 @@ public function validateDataProvider() 2, // current segment id [ [ - 'glue' => 'and', - 'field' => 'leadlist', - 'object' => 'lead', - 'type' => 'leadlist', - 'filter' => [1], - 'display' => null, - 'operator' => 'in', + 'glue' => 'and', + 'field' => 'leadlist', + 'object' => 'lead', + 'type' => 'leadlist', + 'properties' => ['filter' => [1]], + 'display' => null, + 'operator' => 'in', ], [ - 'glue' => 'and', - 'field' => 'leadlist', - 'object' => 'lead', - 'type' => 'leadlist', - 'filter' => [3], - 'display' => null, - 'operator' => 'in', + 'glue' => 'and', + 'field' => 'leadlist', + 'object' => 'lead', + 'type' => 'leadlist', + 'properties' => ['filter' => [3]], + 'display' => null, + 'operator' => 'in', ], ], ], diff --git a/app/bundles/CoreBundle/Tests/Unit/Helper/AbstractFormFieldHelperTest.php b/app/bundles/CoreBundle/Tests/Unit/Helper/AbstractFormFieldHelperTest.php index 751863523e7..1ca0b825c56 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Helper/AbstractFormFieldHelperTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Helper/AbstractFormFieldHelperTest.php @@ -1,14 +1,5 @@ $iterator + * @param mixed[] $options + */ + public function mirror($originDir, $targetDir, ?\Traversable $iterator = null, $options = []): void { Assert::assertSame('/path/to/themes/origin-template-dir', $originDir); Assert::assertSame('/path/to/themes/new-theme-name', $targetDir); @@ -317,7 +317,7 @@ public function readFile(string $filename): string return '{"name":"Origin Theme"}'; } - public function dumpFile($filename, $content) + public function dumpFile($filename, $content): void { Assert::assertSame('/path/to/themes/new-theme-name/config.json', $filename); Assert::assertSame('{"name":"New Theme Name"}', $content); @@ -414,7 +414,10 @@ public function dumpFile($filename, $content) } }, new class() extends Finder { - private $dirs = []; + /** + * @var \SplFileInfo[] + */ + private array $dirs = []; public function __construct() { @@ -429,7 +432,10 @@ public function in($dirs) return $this; } - public function getIterator() + /** + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->dirs); } diff --git a/app/bundles/CoreBundle/Tests/Unit/Helper/TrailingSlashHelperTest.php b/app/bundles/CoreBundle/Tests/Unit/Helper/TrailingSlashHelperTest.php index 428476e5aa7..d6f098ee7cf 100644 --- a/app/bundles/CoreBundle/Tests/Unit/Helper/TrailingSlashHelperTest.php +++ b/app/bundles/CoreBundle/Tests/Unit/Helper/TrailingSlashHelperTest.php @@ -1,14 +1,5 @@ " mautic.core.config.form.update.stability="Update stability level" mautic.core.config.form.update.stability.tooltip="Set the minimum stability level required for updates" mautic.core.config.form.webroot="Mautic's root URL" @@ -134,6 +138,7 @@ mautic.core.config.header.cors="CORS Settings" mautic.core.config.header.defaults="System Defaults" mautic.core.config.header.general="General Settings" mautic.core.config.header.misc="Miscellaneous Settings" +mautic.core.config.header.update="Update Settings" mautic.core.config.update_stability.alpha="Alpha" mautic.core.config.update_stability.beta="Beta" mautic.core.config.update_stability.rc="Release Candidate" @@ -426,6 +431,8 @@ mautic.core.update.index="Update Mautic" mautic.core.update.migrating.database.schema="Migrating database schema..." mautic.core.update.no_cache_data="Could not read the cached update data to apply the update." mautic.core.update.now="Update Now" +mautic.core.update.composer="You have Composer updates enabled. This means that you can only update Mautic through the Command Line. Read more in the documentation." +mautic.core.update.ui.deprecated="Updating Mautic through the user interface is deprecated and won’t be supported anymore in Mautic 5.0. For more information and to learn what you can do to prepare, please read the documentation." mautic.core.update.post_message="Post Update Message" mautic.core.update.remove.deleted.files="Removing deleted files" mautic.core.update.schema_updated="Database schema has been updated." diff --git a/app/bundles/CoreBundle/Update/Step/DeleteCacheStep.php b/app/bundles/CoreBundle/Update/Step/DeleteCacheStep.php index 9de60c1259c..950fac29810 100644 --- a/app/bundles/CoreBundle/Update/Step/DeleteCacheStep.php +++ b/app/bundles/CoreBundle/Update/Step/DeleteCacheStep.php @@ -1,14 +1,5 @@ getOption('update-package'))) { + return; + } + $results = $this->updateHelper->runPreUpdateChecks(); $errors = []; diff --git a/app/bundles/CoreBundle/Update/Step/RemoveDeletedFilesStep.php b/app/bundles/CoreBundle/Update/Step/RemoveDeletedFilesStep.php index 236aac7bf09..cf12e91157f 100644 --- a/app/bundles/CoreBundle/Update/Step/RemoveDeletedFilesStep.php +++ b/app/bundles/CoreBundle/Update/Step/RemoveDeletedFilesStep.php @@ -1,14 +1,5 @@ '; var mauticEnv = 'getEnvironment(); ?>'; var mauticLang = getJsLang(); ?>; + var mauticLocale = 'getRequest()->getLocale(); ?>'; var mauticEditorFonts = get('editor_fonts')); ?>; outputSystemScripts(true); ?> diff --git a/app/bundles/CoreBundle/Views/Error/403.html.php b/app/bundles/CoreBundle/Views/Error/403.html.php index ca1f3874dd4..75ece7ec59b 100644 --- a/app/bundles/CoreBundle/Views/Error/403.html.php +++ b/app/bundles/CoreBundle/Views/Error/403.html.php @@ -1,12 +1,4 @@ set('message', 'mautic.core.error.403'); $view->extend('MauticCoreBundle:Error:base.html.php'); diff --git a/app/bundles/CoreBundle/Views/Error/404.html.php b/app/bundles/CoreBundle/Views/Error/404.html.php index f630cfc51b8..7245e84741f 100644 --- a/app/bundles/CoreBundle/Views/Error/404.html.php +++ b/app/bundles/CoreBundle/Views/Error/404.html.php @@ -1,13 +1,5 @@ set('mautibot', 'openMouth'); $view['slots']->set('message', 'mautic.core.error.404'); $view->extend('MauticCoreBundle:Error:base.html.php'); diff --git a/app/bundles/CoreBundle/Views/Error/500.html.php b/app/bundles/CoreBundle/Views/Error/500.html.php index dbfbc254116..39ae32c9c0e 100644 --- a/app/bundles/CoreBundle/Views/Error/500.html.php +++ b/app/bundles/CoreBundle/Views/Error/500.html.php @@ -1,13 +1,5 @@ set('mautibot', 'openMouth'); $view['slots']->set('message', 'mautic.core.error.500'); $view->extend('MauticCoreBundle:Error:base.html.php'); diff --git a/app/bundles/CoreBundle/Views/Exception/403.html.php b/app/bundles/CoreBundle/Views/Exception/403.html.php index 7e6234ca9f6..dd740b64606 100644 --- a/app/bundles/CoreBundle/Views/Exception/403.html.php +++ b/app/bundles/CoreBundle/Views/Exception/403.html.php @@ -1,12 +1,4 @@ set('message', 'mautic.core.error.403'); $view->extend('MauticCoreBundle:Exception:base.html.php'); diff --git a/app/bundles/CoreBundle/Views/Exception/404.html.php b/app/bundles/CoreBundle/Views/Exception/404.html.php index b53860c4762..22cc2e003c5 100644 --- a/app/bundles/CoreBundle/Views/Exception/404.html.php +++ b/app/bundles/CoreBundle/Views/Exception/404.html.php @@ -1,13 +1,5 @@ set('mautibot', 'openMouth'); $view['slots']->set('message', 'mautic.core.error.404'); $view->extend('MauticCoreBundle:Exception:base.html.php'); diff --git a/app/bundles/CoreBundle/Views/Exception/500.html.php b/app/bundles/CoreBundle/Views/Exception/500.html.php index 2ae18f16d51..6eef53a6261 100644 --- a/app/bundles/CoreBundle/Views/Exception/500.html.php +++ b/app/bundles/CoreBundle/Views/Exception/500.html.php @@ -1,13 +1,5 @@ set('mautibot', 'openMouth'); $view['slots']->set('message', 'mautic.core.error.500'); $view->extend('MauticCoreBundle:Exception:base.html.php'); diff --git a/app/bundles/CoreBundle/Views/Exception/traces.html.php b/app/bundles/CoreBundle/Views/Exception/traces.html.php index f925229900c..d5e0b8a96cf 100644 --- a/app/bundles/CoreBundle/Views/Exception/traces.html.php +++ b/app/bundles/CoreBundle/Views/Exception/traces.html.php @@ -1,13 +1,5 @@ $item) { diff --git a/app/bundles/CoreBundle/Views/FormTheme/Config/_config_coreconfig_widget.html.php b/app/bundles/CoreBundle/Views/FormTheme/Config/_config_coreconfig_widget.html.php index 783d4f753ef..7bfddfe9779 100644 --- a/app/bundles/CoreBundle/Views/FormTheme/Config/_config_coreconfig_widget.html.php +++ b/app/bundles/CoreBundle/Views/FormTheme/Config/_config_coreconfig_widget.html.php @@ -13,7 +13,7 @@ $template = '

{content}
'; ?> - +

trans('mautic.core.config.header.general'); ?>

@@ -23,7 +23,6 @@ rowIfExists($fields, 'site_url', $template); ?> rowIfExists($fields, 'webroot', $template); ?> rowIfExists($fields, '404_page', $template); ?> - rowIfExists($fields, 'update_stability', $template); ?> rowIfExists($fields, 'cache_path', $template); ?> rowIfExists($fields, 'log_path', $template); ?> rowIfExists($fields, 'theme', $template); ?> @@ -138,3 +137,26 @@
+ + +
+
+

trans('mautic.core.config.header.update'); ?>

+
+
+
+ rowIfExists($fields, 'update_stability', $template); ?> + rowIfExists( + $fields, + 'composer_updates', + '
{content}'.$view['translator']->trans( + 'mautic.core.config.form.update.composer.warning', + [ + '%url_start%' => '', + '%url_end%' => '', + ]).'
' + ); ?> +
+
+
+ diff --git a/app/bundles/CoreBundle/Views/FormTheme/entity_properties.html.php b/app/bundles/CoreBundle/Views/FormTheme/entity_properties.html.php index e568741c197..cec9064e01c 100644 --- a/app/bundles/CoreBundle/Views/FormTheme/entity_properties.html.php +++ b/app/bundles/CoreBundle/Views/FormTheme/entity_properties.html.php @@ -1,14 +1,5 @@ addButtons($customButtons); } diff --git a/app/bundles/CoreBundle/Views/Helper/blank_form.html.php b/app/bundles/CoreBundle/Views/Helper/blank_form.html.php index 0b2a0bc673a..db8accac595 100644 --- a/app/bundles/CoreBundle/Views/Helper/blank_form.html.php +++ b/app/bundles/CoreBundle/Views/Helper/blank_form.html.php @@ -1,13 +1,5 @@ extend('MauticCoreBundle:Menu:main.html.php'); diff --git a/app/bundles/CoreBundle/Views/Menu/extra.html.php b/app/bundles/CoreBundle/Views/Menu/extra.html.php index ff16e870e85..e143e55f187 100644 --- a/app/bundles/CoreBundle/Views/Menu/extra.html.php +++ b/app/bundles/CoreBundle/Views/Menu/extra.html.php @@ -1,13 +1,5 @@ hasChildren() && 0 !== $options['depth'] && $item->getDisplayChildren()) { $childrenAttributes = $item->getChildrenAttributes(); if (!isset($childrenAttributes['class'])) { diff --git a/app/bundles/CoreBundle/Views/Menu/main.html.php b/app/bundles/CoreBundle/Views/Menu/main.html.php index bd28fda4068..ee4ef08f5b0 100644 --- a/app/bundles/CoreBundle/Views/Menu/main.html.php +++ b/app/bundles/CoreBundle/Views/Menu/main.html.php @@ -1,13 +1,5 @@ hasChildren() && 0 !== $options['depth'] && $item->getDisplayChildren()) { /* Top menu level start */ if ($item->isRoot()) { diff --git a/app/bundles/CoreBundle/Views/Menu/profile_inline.html.php b/app/bundles/CoreBundle/Views/Menu/profile_inline.html.php index 711098e9fc5..e5e7e5d31b8 100644 --- a/app/bundles/CoreBundle/Views/Menu/profile_inline.html.php +++ b/app/bundles/CoreBundle/Views/Menu/profile_inline.html.php @@ -1,13 +1,5 @@ hasChildren() && 0 !== $options['depth'] && $item->getDisplayChildren()) { foreach ($item->getChildren() as $child) { if (!$child->isDisplayed()) { diff --git a/app/bundles/CoreBundle/Views/Standard/form.html.php b/app/bundles/CoreBundle/Views/Standard/form.html.php index 04f73875123..ef91f0137d0 100644 --- a/app/bundles/CoreBundle/Views/Standard/form.html.php +++ b/app/bundles/CoreBundle/Views/Standard/form.html.php @@ -1,14 +1,5 @@ extend('MauticCoreBundle:FormTheme:form_simple.html.php'); // Parse standard fields diff --git a/app/bundles/CoreBundle/Views/Update/index.html.php b/app/bundles/CoreBundle/Views/Update/index.html.php index ed2f9f73429..83097386916 100644 --- a/app/bundles/CoreBundle/Views/Update/index.html.php +++ b/app/bundles/CoreBundle/Views/Update/index.html.php @@ -11,6 +11,9 @@ $view->extend('MauticCoreBundle:Default:content.html.php'); $view['slots']->set('mauticContent', 'update'); $view['slots']->set('headerTitle', $view['translator']->trans('mautic.core.update.index')); + +/** @var bool $isComposerEnabled */ +$isComposerEnabled = $isComposerEnabled; ?>
@@ -46,9 +49,18 @@ + +
+ trans('mautic.core.update.composer'); ?> +
+ +
+ trans('mautic.core.update.ui.deprecated'); ?> +
+
diff --git a/app/bundles/DashboardBundle/Config/config.php b/app/bundles/DashboardBundle/Config/config.php index 7ef36355ece..dfbb14e7061 100644 --- a/app/bundles/DashboardBundle/Config/config.php +++ b/app/bundles/DashboardBundle/Config/config.php @@ -1,14 +1,5 @@ [ 'main' => [ diff --git a/app/bundles/DashboardBundle/Controller/AjaxController.php b/app/bundles/DashboardBundle/Controller/AjaxController.php index 029e3e70d11..21fe9594bbd 100644 --- a/app/bundles/DashboardBundle/Controller/AjaxController.php +++ b/app/bundles/DashboardBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ ').val(index).text(value).appendTo(filterEl); + }); + mQuery('.' + index).wrapAll(""); + } else { + mQuery('"); + } + + // Destroy the chosen and recreate + Mautic.destroyChosen(mQuery(filterId)); + + mQuery(filterId).attr('data-placeholder', placeholder); + + Mautic.activateChosenSelect(mQuery(filterId)); + } }; Mautic.standardDynamicContentUrl = function(options) { diff --git a/app/bundles/DynamicContentBundle/Config/config.php b/app/bundles/DynamicContentBundle/Config/config.php index 3c5f36b94c1..566b636ea58 100644 --- a/app/bundles/DynamicContentBundle/Config/config.php +++ b/app/bundles/DynamicContentBundle/Config/config.php @@ -1,14 +1,5 @@ [ 'main' => [ @@ -163,6 +154,7 @@ 'mautic.dynamicContent.model.dynamicContent', 'mautic.campaign.executioner.realtime', 'event_dispatcher', + 'mautic.lead.model.lead', ], ], ], diff --git a/app/bundles/DynamicContentBundle/Controller/AjaxController.php b/app/bundles/DynamicContentBundle/Controller/AjaxController.php index a1ae8d18926..fbb00e76ec8 100644 --- a/app/bundles/DynamicContentBundle/Controller/AjaxController.php +++ b/app/bundles/DynamicContentBundle/Controller/AjaxController.php @@ -1,14 +1,5 @@ filters = $filters; + $this->contact = $contact; + } + + public function isMatch(): bool + { + return $this->isEvaluated() ? $this->isMatched : false; + } + + public function isEvaluated(): bool + { + return $this->isEvaluated; + } + + public function setIsEvaluated(bool $evaluated): ContactFiltersEvaluateEvent + { + $this->isEvaluated = $evaluated; + + return $this; + } + + public function getContact(): Lead + { + return $this->contact; + } + + public function setContact(Lead $contact): ContactFiltersEvaluateEvent + { + $this->contact = $contact; + + return $this; + } + + /** + * @return mixed[] + */ + public function getFilters(): array + { + return $this->filters; + } + + public function isMatched(): bool + { + return $this->isMatched; + } + + public function setIsMatched(bool $isMatched): ContactFiltersEvaluateEvent + { + $this->isMatched = $isMatched; + + return $this; + } +} diff --git a/app/bundles/DynamicContentBundle/Event/DynamicContentEvent.php b/app/bundles/DynamicContentBundle/Event/DynamicContentEvent.php index d50091109a4..9c7b0845878 100644 --- a/app/bundles/DynamicContentBundle/Event/DynamicContentEvent.php +++ b/app/bundles/DynamicContentBundle/Event/DynamicContentEvent.php @@ -1,14 +1,5 @@ dynamicContentModel = $dynamicContentModel; $this->realTimeExecutioner = $realTimeExecutioner; $this->dispatcher = $dispatcher; + $this->leadModel = $leadModel; } /** - * @param $slot + * @param string $slot * @param Lead|array $lead * * @return string @@ -97,7 +84,7 @@ public function getDynamicContentSlotForLead($slotName, $lead) if ($dwc->getIsCampaignBased()) { continue; } - if ($lead && $this->matchFilterForLead($dwc->getFilters(), $leadArray)) { + if ($lead && $this->filtersMatchContact($dwc->getFilters(), $leadArray)) { return $lead ? $this->getRealDynamicContent($dwc->getSlotName(), $lead, $dwc) : ''; } } @@ -170,9 +157,8 @@ public function findDwcTokens($content, $lead) } /** - * @param $slot - * @param $lead - * @param $dwc + * @param string $slot + * @param Lead|mixed[] $lead * * @return string */ @@ -248,4 +234,29 @@ function (Tag $v) { ] ); } + + /** + * @param mixed[] $filters + * @param mixed[] $contactArray + */ + private function filtersMatchContact(array $filters, array $contactArray): bool + { + if (empty($contactArray['id'])) { + return false; + } + + // We attempt even listeners first + if ($this->dispatcher->hasListeners(DynamicContentEvents::ON_CONTACTS_FILTER_EVALUATE)) { + /** @var Lead $contact */ + $contact = $this->leadModel->getEntity($contactArray['id']); + + $event = new ContactFiltersEvaluateEvent($filters, $contact); + $this->dispatcher->dispatch(DynamicContentEvents::ON_CONTACTS_FILTER_EVALUATE, $event); + if ($event->isMatch()) { + return true; + } + } + + return $this->matchFilterForLead($filters, $contactArray); + } } diff --git a/app/bundles/DynamicContentBundle/MauticDynamicContentBundle.php b/app/bundles/DynamicContentBundle/MauticDynamicContentBundle.php index 93c4c27484a..e02f13f3d24 100644 --- a/app/bundles/DynamicContentBundle/MauticDynamicContentBundle.php +++ b/app/bundles/DynamicContentBundle/MauticDynamicContentBundle.php @@ -1,14 +1,5 @@ getMockBuilder(DynamicContentModel::class) - ->disableOriginalConstructor() - ->onlyMethods(['getEntities']) - ->getMock(); + $this->mockModel = $this->createMock(DynamicContentModel::class); + $this->realTimeExecutioner = $this->createMock(RealTimeExecutioner::class); + $this->mockDispatcher = $this->createMock(EventDispatcher::class); + $this->leadModel = $this->createMock(LeadModel::class); + $this->helper = new DynamicContentHelper( + $this->mockModel, + $this->realTimeExecutioner, + $this->mockDispatcher, + $this->leadModel + ); + } - $mockModel->expects($this->exactly(2)) + public function testGetDwcBySlotNameWithPublished(): void + { + $this->mockModel->expects($this->exactly(2)) ->method('getEntities') ->withConsecutive( [ @@ -66,15 +99,194 @@ public function testGetDwcBySlotNameWithPublished(): void ) ->willReturnOnConsecutiveCalls(true, false); - $realTimeExecutioner = $this->createMock(RealTimeExecutioner::class); - $mockDispatcher = $this->createMock(EventDispatcher::class); - - $fixture = new DynamicContentHelper($mockModel, $realTimeExecutioner, $mockDispatcher); - // Only get published - $this->assertTrue($fixture->getDwcsBySlotName('test', true)); + $this->assertTrue($this->helper->getDwcsBySlotName('test', true)); // Get all - $this->assertFalse($fixture->getDwcsBySlotName('secondtest')); + $this->assertFalse($this->helper->getDwcsBySlotName('secondtest')); + } + + public function testGetDynamicContentSlotForLeadWithListenerFindingMatch(): void + { + $slotName = 'test'; + $contact = new Lead(); + $contact->setFields(['email' => 'ma@ka.t', 'id' => 123]); + + $slot = new DynamicContent(); + $slot->setName($slotName); + $slot->setIsCampaignBased(false); + // Setting filter that is not known to Mautic, but is for a plugin. + $slot->setFilters([['field' => 'unicorn', 'type' => 'text', 'operator' => '=', 'filter' => 'magic']]); + $slot->setContent('

test

'); + + $this->mockModel->method('getEntities') + ->willReturn([$slot]); + + $this->mockModel->method('getTranslatedEntity') + ->willReturn([$slot, $slot]); + + $this->leadModel->method('getEntity') + ->with(123) + ->willReturn($contact); + + $this->mockDispatcher->method('hasListeners')->willReturn(true); + $this->mockDispatcher->expects($this->exactly(2)) + ->method('dispatch') + ->withConsecutive( + [ + DynamicContentEvents::ON_CONTACTS_FILTER_EVALUATE, + $this->callback( + function (ContactFiltersEvaluateEvent $event) use ($contact, $slot) { + $this->assertSame($contact, $event->getContact()); + $this->assertSame($slot->getFilters(), $event->getFilters()); + + $event->setIsEvaluated(true); + $event->setIsMatched(true); // Match found in a subscriber. + + return true; + } + ), + ], + [ + DynamicContentEvents::TOKEN_REPLACEMENT, + $this->callback( + function (TokenReplacementEvent $event) use ($contact, $slot) { + $this->assertSame($contact, $event->getLead()); + $this->assertSame($slot->getContent(), $event->getContent()); + + return true; + } + ), + ] + ); + + Assert::assertSame( + '

test

', + $this->helper->getDynamicContentSlotForLead($slotName, $contact) + ); + } + + public function testGetDynamicContentSlotForLeadWithListenerNotFindingMatch(): void + { + $slotName = 'test'; + $contact = new Lead(); + $contact->setFields(['email' => 'ma@ka.t', 'id' => 123]); + + $slot = new DynamicContent(); + $slot->setName($slotName); + $slot->setIsCampaignBased(false); + // Setting filter that is not known to Mautic, nor any plugin. + $slot->setFilters([['field' => 'unicorn', 'type' => 'text', 'operator' => '=', 'filter' => 'magic']]); + $slot->setContent('

test

'); + + $this->mockModel->method('getEntities') + ->willReturn([$slot]); + + $this->mockModel->method('getTranslatedEntity') + ->willReturn([$slot, $slot]); + + $this->leadModel->method('getEntity') + ->with(123) + ->willReturn($contact); + + $this->mockDispatcher->method('hasListeners')->willReturn(true); + $this->mockDispatcher->expects($this->once()) + ->method('dispatch') + ->withConsecutive( + [ + DynamicContentEvents::ON_CONTACTS_FILTER_EVALUATE, + $this->callback( + function (ContactFiltersEvaluateEvent $event) use ($contact, $slot) { + $this->assertSame($contact, $event->getContact()); + $this->assertSame($slot->getFilters(), $event->getFilters()); + + // Match not found in any subscriber. + + return true; + } + ), + ] + ); + + Assert::assertSame( + '', // No content returned as the filter did not match anything. + $this->helper->getDynamicContentSlotForLead($slotName, $contact) + ); + } + + public function testGetDynamicContentSlotForLeadWithNoListenerWithMatchingFilter(): void + { + $slotName = 'test'; + $contact = new Lead(); + $contact->setFields(['email' => 'ma@ka.t', 'id' => 123]); + + $slot = new DynamicContent(); + $slot->setName($slotName); + $slot->setIsCampaignBased(false); + $slot->setFilters([['field' => 'email', 'type' => 'email', 'operator' => '=', 'filter' => 'ma@ka.t']]); + $slot->setContent('

test

'); + + $this->mockModel->method('getEntities') + ->willReturn([$slot]); + + $this->mockModel->method('getTranslatedEntity') + ->willReturn([$slot, $slot]); + + $this->leadModel->method('getEntity') + ->with(123) + ->willReturn($contact); + + $this->mockDispatcher->method('hasListeners')->willReturn(false); + $this->mockDispatcher->expects($this->once()) + ->method('dispatch') + ->withConsecutive( + [ + DynamicContentEvents::TOKEN_REPLACEMENT, + $this->callback( + function (TokenReplacementEvent $event) use ($contact, $slot) { + $this->assertSame($contact, $event->getLead()); + $this->assertSame($slot->getContent(), $event->getContent()); + + return true; + } + ), + ] + ); + + Assert::assertSame( + '

test

', + $this->helper->getDynamicContentSlotForLead($slotName, $contact) + ); + } + + public function testGetDynamicContentSlotForLeadWithNoListenerWithNotMatchingFilter(): void + { + $slotName = 'test'; + $contact = new Lead(); + $contact->setFields(['email' => 'ma@ka.t', 'id' => 123]); + + $slot = new DynamicContent(); + $slot->setName($slotName); + $slot->setIsCampaignBased(false); + $slot->setFilters([['field' => 'email', 'type' => 'email', 'operator' => '=', 'filter' => 'uni@co.rn']]); + $slot->setContent('

test

'); + + $this->mockModel->method('getEntities') + ->willReturn([$slot]); + + $this->mockModel->method('getTranslatedEntity') + ->willReturn([$slot, $slot]); + + $this->leadModel->method('getEntity') + ->with(123) + ->willReturn($contact); + + $this->mockDispatcher->method('hasListeners')->willReturn(false); + $this->mockDispatcher->expects($this->never())->method('dispatch'); + + Assert::assertSame( + '', + $this->helper->getDynamicContentSlotForLead($slotName, $contact) + ); } } diff --git a/app/bundles/DynamicContentBundle/Views/DynamicContent/form.html.php b/app/bundles/DynamicContentBundle/Views/DynamicContent/form.html.php index f9d1eb19a47..6dbafe65102 100644 --- a/app/bundles/DynamicContentBundle/Views/DynamicContent/form.html.php +++ b/app/bundles/DynamicContentBundle/Views/DynamicContent/form.html.php @@ -111,9 +111,9 @@ $list = (!empty($params['properties']['list'])) ? $params['properties']['list'] : []; $choices = ('boolean' === $params['properties']['type']) ? - \Mautic\LeadBundle\Helper\FormFieldHelper::parseBooleanList($list) + array_flip(\Mautic\LeadBundle\Helper\FormFieldHelper::parseBooleanList($list)) : - \Mautic\LeadBundle\Helper\FormFieldHelper::parseList($list); + \Mautic\LeadBundle\Helper\FormFieldHelper::parseListForChoices($list); $list = json_encode($choices); $callback = (!empty($params['properties']['callback'])) ? $params['properties']['callback'] : ''; diff --git a/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_entry_widget.html.php b/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_entry_widget.html.php index 840e0a4252a..d1880d21981 100644 --- a/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_entry_widget.html.php +++ b/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_entry_widget.html.php @@ -9,4 +9,53 @@ * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html */ -$view->extend('MauticLeadBundle:FormTheme:Filter\\_leadlist_filters_entry_widget.html.php'); +$isPrototype = ('__name__' == $form->vars['name']); +$filterType = $form['field']->vars['value']; +$inGroup = (isset($form->vars['data']['glue']) && 'and' === $form->vars['data']['glue']); +$isBehavior = isset($fields['behaviors'][$filterType]['label']); +$class = (isset($form->vars['data']['object']) && 'company' == $form->vars['data']['object']) ? 'fa-building' : 'fa-user'; + +if ($isBehavior) { + $object = 'behaviors'; +} else { + $object = (isset($form->vars['data']['object'])) ? $form->vars['data']['object'] : 'lead'; +} + +if (!$isPrototype && !isset($fields[$object][$filterType]['label'])) { + return; +} +?> + +
+
+
+ widget($form['glue']); ?> +
+
+
+
+ +
+ +
+ widget($form['operator']); ?> +
+ + vars['errors']) || count($form['display']->vars['errors']); ?> +
+ widget($form['filter']); ?> + widget($form['display']); ?> + errors($form['filter']); ?> + errors($form['display']); ?> +
+ +
+ +
+ widget($form['field']); ?> + widget($form['type']); ?> + widget($form['object']); ?> +
+
diff --git a/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_widget.html.php b/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_widget.html.php index 4ce48d40ce8..beed5f42f1b 100644 --- a/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_widget.html.php +++ b/app/bundles/DynamicContentBundle/Views/FormTheme/Filter/_dwc_filters_widget.html.php @@ -1,12 +1,3 @@ extend('MauticLeadBundle:FormTheme:Filter\\_leadlist_filters_widget.html.php'); diff --git a/app/bundles/EmailBundle/Assets/js/email.js b/app/bundles/EmailBundle/Assets/js/email.js index f690c87c267..6379f846e4f 100644 --- a/app/bundles/EmailBundle/Assets/js/email.js +++ b/app/bundles/EmailBundle/Assets/js/email.js @@ -697,7 +697,7 @@ Mautic.addDynamicContentFilter = function (selectedFilter, jQueryVariant) { var operators = mQuery(selectedOption).data('field-operators'); mQuery('#' + filterIdBase + '_operator').html(''); - mQuery.each(operators, function (value, label) { + mQuery.each(operators, function (label, value) { var newOption = mQuery('"); - } + var fieldAlias = mQuery('#leadlist_filters_'+filterNum+'_field'); + var fieldObject = mQuery('#leadlist_filters_'+filterNum+'_object'); + var filterValue = mQuery('#leadlist_filters_'+filterNum+'_properties_filter').val(); + var filterId = '#leadlist_filters_' + filterNum + '_properties_filter'; - // Destroy the chosen and recreate - Mautic.destroyChosen(mQuery(filterId)); + Mautic.loadFilterForm(filterNum, fieldObject.val(), fieldAlias.val(), operatorSelect.val(), function(propertiesFields) { + var selector = '#leadlist_filters_'+filterNum; + mQuery(selector+'_properties').html(propertiesFields); - mQuery(filterId).attr('data-placeholder', placeholder); - - Mautic.activateChosenSelect(mQuery(filterId)); - } + Mautic.triggerOnPropertiesFormLoadedEvent(selector, filterValue); + }); - Mautic.setProcessorForFilterValue(filterId, operator); + Mautic.setProcessorForFilterValue(filterId, operatorSelect.val()); }; Mautic.setFilterValuesProcessor = function () { @@ -496,10 +523,14 @@ Mautic.setProcessorForFilterValue = function (filterId, operator) { } }; -Mautic.updateLookupListFilter = function(field, datum) { - if (datum && datum.id) { +/** + * Adds values to the lookup_id form after user selects a typeahead option. + */ +Mautic.updateLookupListFilter = function(field, item) { + if (item && item.id) { var filterField = '#'+field.replace('_display', '_filter'); - mQuery(filterField).val(datum.id); + mQuery(filterField).val(item.id); + mQuery(field).val(item.name); } }; @@ -507,7 +538,7 @@ Mautic.activateSegmentFilterTypeahead = function(displayId, filterId, fieldOptio var mQueryBackup = mQuery; - if(typeof mQueryObject == 'function'){ + if (typeof mQueryObject === 'function') { mQuery = mQueryObject; } @@ -518,11 +549,37 @@ Mautic.activateSegmentFilterTypeahead = function(displayId, filterId, fieldOptio mQuery = mQueryBackup; }; +Mautic.loadFilterForm = function(filterNum, fieldObject, fieldAlias, operator, resultHtml, search = null) { + mQuery.ajax({ + showLoadingBar: true, + url: mauticAjaxUrl, + type: 'POST', + data: { + action: 'lead:loadSegmentFilterForm', + fieldAlias: fieldAlias, + fieldObject: fieldObject, + operator: operator, + filterNum: filterNum, + search: search, + }, + dataType: 'json', + success: function (response) { + Mautic.stopPageLoadingBar(); + resultHtml(response.viewParameters.form); + if (fieldAlias == 'lead_asset_download') { + Mautic.handleAssetDownloadSearch(filterNum, fieldObject, fieldAlias, operator, resultHtml, search); + } + }, + error: function (request, textStatus, errorThrown) { + Mautic.processAjaxError(request, textStatus, errorThrown); + } + }); +} + Mautic.addLeadListFilter = function (elId, elObj) { var filterId = '#available_' + elObj + '_' + elId; var filterOption = mQuery(filterId); var label = filterOption.text(); - var alias = filterOption.val(); // Create a new filter @@ -532,7 +589,6 @@ Mautic.addLeadListFilter = function (elId, elObj) { var prototypeStr = mQuery('.available-filters').data('prototype'); var fieldType = filterOption.data('field-type'); var fieldObject = filterOption.data('field-object'); - var isSpecial = (mQuery.inArray(fieldType, ['leadlist', 'campaign', 'assets', 'device_type', 'device_brand', 'device_os', 'lead_email_received', 'lead_email_sent', 'tags', 'multiselect', 'boolean', 'select', 'country', 'timezone', 'region', 'stage', 'locale', 'globalcategory']) != -1); prototypeStr = prototypeStr.replace(/__name__/g, filterNum); prototypeStr = prototypeStr.replace(/__label__/g, label); @@ -549,18 +605,6 @@ Mautic.addLeadListFilter = function (elId, elObj) { var filterBase = prefix + "[filters][" + filterNum + "]"; var filterIdBase = prefix + "_filters_" + filterNum + "_"; - if (isSpecial) { - var templateField = fieldType; - if (fieldType == 'boolean' || fieldType == 'multiselect') { - templateField = 'select'; - } - - var template = mQuery('#templates .' + templateField + '-template').clone(); - template.attr('name', mQuery(template).attr('name').replace(/__name__/g, filterNum)); - template.attr('id', mQuery(template).attr('id').replace(/__name__/g, filterNum)); - prototype.find('input[name="' + filterBase + '[filter]"]').replaceWith(template); - } - if (mQuery('#' + prefix + '_filters div.panel').length == 0) { // First filter so hide the glue footer prototype.find(".panel-heading").addClass('hide'); @@ -587,93 +631,8 @@ Mautic.addLeadListFilter = function (elId, elObj) { prototype.find("input[name='" + filterBase + "[field]']").val(elId); prototype.find("input[name='" + filterBase + "[type]']").val(fieldType); prototype.find("input[name='" + filterBase + "[object]']").val(fieldObject); - - var filterEl = (isSpecial) ? "select[name='" + filterBase + "[filter]']" : "input[name='" + filterBase + "[filter]']"; - prototype.appendTo('#' + prefix + '_filters'); - var filter = mQuery('#' + filterIdBase + 'filter'); - - //activate fields - if (isSpecial) { - if (fieldType == 'select' || fieldType == 'multiselect' || fieldType == 'boolean') { - // Generate the options - var fieldOptions = filterOption.data("field-list"); - mQuery.each(fieldOptions, function(index, val) { - if (mQuery.isPlainObject(val)) { - var optGroup = index; - mQuery.each(val, function(index, value) { - mQuery('"); - } else { - mQuery('