From 201e6c4737325aaca74147133a25d8ec21cdace0 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 12:18:48 +0100 Subject: [PATCH 01/66] feat: add ddev config --- .ddev/config.yaml | 280 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 .ddev/config.yaml diff --git a/.ddev/config.yaml b/.ddev/config.yaml new file mode 100644 index 0000000..1e43051 --- /dev/null +++ b/.ddev/config.yaml @@ -0,0 +1,280 @@ +name: xm-formcycle +type: typo3 +docroot: public +php_version: "8.0" +webserver_type: nginx-fpm +router_http_port: "80" +router_https_port: "443" +xdebug_enabled: false +additional_hostnames: [] +additional_fqdns: [] +database: + type: mariadb + version: "10.11" +use_dns_when_possible: true +composer_version: "2" +web_environment: [] +nodejs_version: "20" + +# Key features of DDEV's config.yaml: + +# name: # Name of the project, automatically provides +# http://projectname.ddev.site and https://projectname.ddev.site + +# type: # backdrop, craftcms, django4, drupal6/7/8/9/10, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress +# See https://ddev.readthedocs.io/en/latest/users/quickstart/ for more +# information on the different project types + +# docroot: # Relative path to the directory containing index.php. + +# php_version: "8.1" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3" + +# You can explicitly specify the webimage but this +# is not recommended, as the images are often closely tied to DDEV's' behavior, +# so this can break upgrades. + +# webimage: # nginx/php docker image. + +# database: +# type: # mysql, mariadb, postgres +# version: # database version, like "10.4" or "8.0" +# MariaDB versions can be 5.5-10.8 and 10.11, MySQL versions can be 5.5-8.0 +# PostgreSQL versions can be 9-16. + +# router_http_port: # Port to be used for http (defaults to global configuration, usually 80) +# router_https_port: # Port for https (defaults to global configuration, usually 443) + +# xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, +# as leaving Xdebug enabled all the time is a big performance hit. + +# xhprof_enabled: false # Set to true to enable Xhprof and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xhprof" to enable Xhprof and "ddev xhprof off" to disable it work better, +# as leaving Xhprof enabled all the time is a big performance hit. + +# webserver_type: nginx-fpm, apache-fpm, or nginx-gunicorn + +# timezone: Europe/Berlin +# This is the timezone used in the containers and by PHP; +# it can be set to any valid timezone, +# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# For example Europe/Dublin or MST7MDT + +# composer_root: +# Relative path to the Composer root directory from the project root. This is +# the directory which contains the composer.json and where all Composer related +# commands are executed. + +# composer_version: "2" +# You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1 +# to use the latest major version available at the time your container is built. +# It is also possible to use each other Composer version channel. This includes: +# - 2.2 (latest Composer LTS version) +# - stable +# - preview +# - snapshot +# Alternatively, an explicit Composer version may be specified, for example "2.2.18". +# To reinstall Composer after the image was built, run "ddev debug refresh". + +# nodejs_version: "18" +# change from the default system Node.js version to any other version. +# Numeric version numbers can be complete (i.e. 18.15.0) or +# incomplete (18, 17.2, 16). 'lts' and 'latest' can be used as well along with +# other named releases. +# see https://www.npmjs.com/package/n#specifying-nodejs-versions +# Note that you can continue using 'ddev nvm' or nvm inside the web container +# to change the project's installed node version if you need to. + +# additional_hostnames: +# - somename +# - someothername +# would provide http and https URLs for "somename.ddev.site" +# and "someothername.ddev.site". + +# additional_fqdns: +# - example.com +# - sub1.example.com +# would provide http and https URLs for "example.com" and "sub1.example.com" +# Please take care with this because it can cause great confusion. + +# upload_dirs: "custom/upload/dir" +# +# upload_dirs: +# - custom/upload/dir +# - ../private +# +# would set the destination paths for ddev import-files to /custom/upload/dir +# When Mutagen is enabled this path is bind-mounted so that all the files +# in the upload_dirs don't have to be synced into Mutagen. + +# disable_upload_dirs_warning: false +# If true, turns off the normal warning that says +# "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" + +# ddev_version_constraint: "" +# Example: +# ddev_version_constraint: ">= 1.22.4" +# This will enforce that the running ddev version is within this constraint. +# See https://github.com/Masterminds/semver#checking-version-constraints for +# supported constraint formats + +# working_dir: +# web: /var/www/html +# db: /home +# would set the default working directory for the web and db services. +# These values specify the destination directory for ddev ssh and the +# directory in which commands passed into ddev exec are run. + +# omit_containers: [db, ddev-ssh-agent] +# Currently only these containers are supported. Some containers can also be +# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit +# the "db" container, several standard features of DDEV that access the +# database container will be unusable. In the global configuration it is also +# possible to omit ddev-router, but not here. + +# performance_mode: "global" +# DDEV offers performance optimization strategies to improve the filesystem +# performance depending on your host system. Should be configured globally. +# +# If set, will override the global config. Possible values are: +# - "global": uses the value from the global config. +# - "none": disables performance optimization for this project. +# - "mutagen": enables Mutagen for this project. +# - "nfs": enables NFS for this project. +# +# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs +# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen + +# fail_on_hook_fail: False +# Decide whether 'ddev start' should be interrupted by a failing hook + +# host_https_port: "59002" +# The host port binding for https can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_webserver_port: "59001" +# The host port binding for the ddev-webserver can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_db_port: "59002" +# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic +# unless explicitly specified. + +# mailpit_http_port: "8025" +# mailpit_https_port: "8026" +# The Mailpit ports can be changed from the default 8025 and 8026 + +# host_mailpit_port: "8025" +# The mailpit port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be bound directly to localhost if specified here. + +# webimage_extra_packages: [php7.4-tidy, php-bcmath] +# Extra Debian packages that are needed in the webimage can be added here + +# dbimage_extra_packages: [telnet,netcat] +# Extra Debian packages that are needed in the dbimage can be added here + +# use_dns_when_possible: true +# If the host has internet access and the domain configured can +# successfully be looked up, DNS will be used for hostname resolution +# instead of editing /etc/hosts +# Defaults to true + +# project_tld: ddev.site +# The top-level domain used for project URLs +# The default "ddev.site" allows DNS lookup via a wildcard +# If you prefer you can change this to "ddev.local" to preserve +# pre-v1.9 behavior. + +# ngrok_args: --basic-auth username:pass1234 +# Provide extra flags to the "ngrok http" command, see +# https://ngrok.com/docs/ngrok-agent/config or run "ngrok http -h" + +# disable_settings_management: false +# If true, DDEV will not create CMS-specific settings files like +# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php +# In this case the user must provide all such settings. + +# You can inject environment variables into the web container with: +# web_environment: +# - SOMEENV=somevalue +# - SOMEOTHERENV=someothervalue + +# no_project_mount: false +# (Experimental) If true, DDEV will not mount the project into the web container; +# the user is responsible for mounting it manually or via a script. +# This is to enable experimentation with alternate file mounting strategies. +# For advanced users only! + +# bind_all_interfaces: false +# If true, host ports will be bound on all network interfaces, +# not the localhost interface only. This means that ports +# will be available on the local network if the host firewall +# allows it. + +# default_container_timeout: 120 +# The default time that DDEV waits for all containers to become ready can be increased from +# the default 120. This helps in importing huge databases, for example. + +#web_extra_exposed_ports: +#- name: nodejs +# container_port: 3000 +# http_port: 2999 +# https_port: 3000 +#- name: something +# container_port: 4000 +# https_port: 4000 +# http_port: 3999 +# Allows a set of extra ports to be exposed via ddev-router +# Fill in all three fields even if you don’t intend to use the https_port! +# If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. +# +# The port behavior on the ddev-webserver must be arranged separately, for example +# using web_extra_daemons. +# For example, with a web app on port 3000 inside the container, this config would +# expose that web app on https://.ddev.site:9999 and http://.ddev.site:9998 +# web_extra_exposed_ports: +# - name: myapp +# container_port: 3000 +# http_port: 9998 +# https_port: 9999 + +#web_extra_daemons: +#- name: "http-1" +# command: "/var/www/html/node_modules/.bin/http-server -p 3000" +# directory: /var/www/html +#- name: "http-2" +# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000" +# directory: /var/www/html + +# override_config: false +# By default, config.*.yaml files are *merged* into the configuration +# But this means that some things can't be overridden +# For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge +# and you can't erase existing hooks or all environment variables. +# However, with "override_config: true" in a particular config.*.yaml file, +# 'use_dns_when_possible: false' can override the existing values, and +# hooks: +# post-start: [] +# or +# web_environment: [] +# or +# additional_hostnames: [] +# can have their intended affect. 'override_config' affects only behavior of the +# config.*.yaml file it exists in. + +# Many DDEV commands can be extended to run tasks before or after the +# DDEV command is executed, for example "post-start", "post-import-db", +# "pre-composer", "post-composer" +# See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more +# information on the commands that can be extended and the tasks you can define +# for them. Example: +#hooks: +# post-start: +# - exec: composer install -d /var/www/html From f5ce3c7912864c34905e6a5eb030ffc66894a27d Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 12:22:50 +0100 Subject: [PATCH 02/66] feat: update composer.json, add editorconfig, use php8.1 --- .ddev/config.yaml | 2 +- .editorconfig | 51 ++++++++++++++++++++++++++++ .gitignore | 3 +- composer.json | 85 +++++++++++++++++++++++++++++------------------ 4 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 .editorconfig diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 1e43051..12fb6bf 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,7 +1,7 @@ name: xm-formcycle type: typo3 docroot: public -php_version: "8.0" +php_version: "8.1" webserver_type: nginx-fpm router_http_port: "80" router_https_port: "443" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7703e27 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,51 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# TS/JS-Files +[*.{ts,js}] +indent_size = 2 + +# JSON-Files +[*.json] +indent_style = tab + +# YAML-Files +[*.{yaml,yml}] +indent_size = 2 + +# NEON-Files +[*.neon] +indent_size = 2 +indent_style = tab + +# package.json +[package.json] +indent_size = 2 + +# TypoScript +[*.{typoscript,tsconfig}] +indent_size = 2 + +# XLF-Files +[*.xlf] +indent_style = tab + +# SQL-Files +[*.sql] +indent_style = tab +indent_size = 2 + +# .htaccess +[{_.htaccess,.htaccess}] +indent_style = tab \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8f2077f..5ed2850 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea +public vendor/ -composer.lock \ No newline at end of file +composer.lock diff --git a/composer.json b/composer.json index e9e9e10..5bcb6bc 100644 --- a/composer.json +++ b/composer.json @@ -1,34 +1,55 @@ { - "name": "xima-media/xm_formcycle", - "type": "typo3-cms-extension", - "description": "Integrator für FORMCYCLE", - "homepage": "https://www.xima.de", - "license": ["GPL-2.0+"], - "keywords": [ - "TYPO3 CMS", - "XIMA", - "FORMCYCLE" - ], - "require": { - "ext-curl": "*", - "typo3/cms-core": "^9.5.0 || ^10.4.0 || ^11.5.0" - }, - "require-dev": { - "thibautselingue/local-php-security-checker-installer": "^1.0" - }, - "autoload": { - "psr-4": { - "Xima\\XmFormcycle\\": "Classes/" - } - }, - "scripts": { - "post-install-cmd": [ - "local-php-security-checker-installer && local-php-security-checker" - ] - }, - "extra": { - "typo3/cms": { - "extension-key": "xm_formcycle" - } - } + "name": "xima-media/xm_formcycle", + "type": "typo3-cms-extension", + "description": "Integrator für FORMCYCLE", + "homepage": "https://www.xima.de", + "license": [ + "GPL-2.0+" + ], + "keywords": [ + "TYPO3 CMS", + "XIMA", + "FORMCYCLE" + ], + "authors": [ + { + "name": "Maik Schneider", + "role": "Developer", + "email": "schneider.maik@me.com" + } + ], + "require": { + "php": "^8.0", + "typo3/cms-core": "^12.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.34.1", + "helmich/typo3-typoscript-lint": "3.2", + "saschaegerer/phpstan-typo3": "^1.1.2", + "ssch/typo3-rector": "^1.3.6", + "symfony/translation": "^6.3", + "typo3/cms-base-distribution": "^12.4" + }, + "autoload": { + "psr-4": { + "Xima\\XmFormcycle\\": "Classes/" + } + }, + "scripts": { + "php:fixer": "php vendor/bin/php-cs-fixer --config=php-cs-fixer.php fix", + "php:stan": "php vendor/bin/phpstan --generate-baseline=phpstan-baseline.neon --allow-empty-baseline", + "typoscript:lint": "php vendor/bin/typoscript-lint" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "typo3/cms-composer-installers": true, + "typo3/class-alias-loader": true + } + }, + "extra": { + "typo3/cms": { + "extension-key": "xm_formcycle" + } + } } From 3c48493819f848e8384f7a256f4e3bd564cec0f7 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 13:38:45 +0100 Subject: [PATCH 03/66] feat: require php8.1, install rector --- .ddev/config.yaml | 4 ++-- .gitignore | 2 ++ composer.json | 5 +++-- rector.php | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 rector.php diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 12fb6bf..8f81ff7 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -2,7 +2,7 @@ name: xm-formcycle type: typo3 docroot: public php_version: "8.1" -webserver_type: nginx-fpm +webserver_type: apache-fpm router_http_port: "80" router_https_port: "443" xdebug_enabled: false @@ -10,7 +10,7 @@ additional_hostnames: [] additional_fqdns: [] database: type: mariadb - version: "10.11" + version: "10.4" use_dns_when_possible: true composer_version: "2" web_environment: [] diff --git a/.gitignore b/.gitignore index 5ed2850..6c0c6bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea public +config +var vendor/ composer.lock diff --git a/composer.json b/composer.json index 5bcb6bc..24d9fba 100644 --- a/composer.json +++ b/composer.json @@ -19,14 +19,15 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", "typo3/cms-core": "^12.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.34.1", + "helhum/typo3-console": "^8.1", "helmich/typo3-typoscript-lint": "3.2", "saschaegerer/phpstan-typo3": "^1.1.2", - "ssch/typo3-rector": "^1.3.6", + "ssch/typo3-rector": "^2.0", "symfony/translation": "^6.3", "typo3/cms-base-distribution": "^12.4" }, diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..fef6750 --- /dev/null +++ b/rector.php @@ -0,0 +1,48 @@ +withConfiguredRule(ExtEmConfRector::class, [ + ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [] + ]) + ->withPaths([ + __DIR__ . '/Classes', + __DIR__ . '/Configuration', + __DIR__ . '/config', + ]) + ->withPhpSets( + true + ) + ->withSets([ + Typo3LevelSetList::UP_TO_TYPO3_12, + ]) + # To have a better analysis from PHPStan, we teach it here some more things + ->withPHPStanConfigs([ + Typo3Option::PHPSTAN_FOR_RECTOR_PATH + ]) + ->withPhpVersion(PhpVersion::PHP_81) + ->withRules([ + ConvertImplicitVariablesToExplicitGlobalsRector::class, + ]) + # If you use importNames(), you should consider excluding some TYPO3 files. + ->withSkip([ + // @see https://github.com/sabbelasichon/typo3-rector/issues/2536 + __DIR__ . '/**/Configuration/ExtensionBuilder/*', + NameImportingPostRector::class => [ + 'ext_localconf.php', + 'ext_tables.php', + 'ClassAliasMap.php', + __DIR__ . '/**/Configuration/*.php', + __DIR__ . '/**/Configuration/**/*.php', + ] + ]) +; From 1d04c2035249ee2849f8159ff9e345aacb389ea7 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 13:40:52 +0100 Subject: [PATCH 04/66] feat: apply rector changes --- Classes/Controller/FormcycleController.php | 19 ++++++++++--------- Classes/Form/Element/StartNewElement.php | 2 +- Classes/Helper/FcHelper.php | 10 +++++----- Classes/Helper/WorkaroundHelper.php | 2 +- Configuration/TCA/Overrides/sys_template.php | 5 +++-- Configuration/TCA/Overrides/tt_content.php | 8 +++++--- ext_emconf.php | 3 --- ext_localconf.php | 8 ++++---- ext_tables.php | 2 +- rector.php | 14 ++++++++------ 10 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Classes/Controller/FormcycleController.php b/Classes/Controller/FormcycleController.php index 4969988..6265f73 100644 --- a/Classes/Controller/FormcycleController.php +++ b/Classes/Controller/FormcycleController.php @@ -2,6 +2,7 @@ namespace Xima\XmFormcycle\Controller; +use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; @@ -64,7 +65,7 @@ public function initializeListAction() * * @return void */ - public function listAction() + public function listAction(): ResponseInterface { $viewVars = []; @@ -106,6 +107,7 @@ public function listAction() ]); $this->view->assignMultiple($viewVars); + return $this->htmlResponse(); } /** @@ -114,7 +116,7 @@ public function listAction() public function initializeFormContentAction() { /** @var WorkaroundHelper $workarounds */ - $workarounds = $this->objectManager->get('Xima\\XmFormcycle\\Helper\\WorkaroundHelper'); + $workarounds = $this->objectManager->get(WorkaroundHelper::class); $args = $this->request->getArguments(); $this->settings = array_merge( @@ -126,9 +128,10 @@ public function initializeFormContentAction() /** * */ - public function formContentAction() + public function formContentAction(): ResponseInterface { $this->view->assignMultiple($this->getDirectly(true)); + return $this->htmlResponse(); } /** @@ -146,7 +149,6 @@ protected function getDirectly($frontendServerUrl = false) } /** - * @param \Xima\XmFormcycle\Helper\FcHelper $fch * @param string $fcParams * @return string */ @@ -159,7 +161,7 @@ protected function getFcUrl(FcHelper $fch, $fcParams = '') $useui = $this->settings['xf']['useFcjQueryUi']; $usebs = $this->settings['xf']['useFcBootStrap']; $selProjectId = $this->settings['xf']['xfc_p_id']; - $frontendLang = $GLOBALS['TSFE']->sys_language_isocode ?: 'de'; + $frontendLang = $GLOBALS['TSFE']->getLanguage()->getTwoLetterIsoCode() ?: 'de'; $fcParams .= $this->settings['xf']['useFcUrlParams']; $fcParams = $this->resolveCustomParameters($fcParams); @@ -214,12 +216,12 @@ function getRedirectURL($uid) function url_origin($s, $use_forwarded_host = false) { $ssl = (!empty($s['HTTPS']) && $s['HTTPS'] == 'on') ? true : false; - $sp = strtolower($s['SERVER_PROTOCOL']); + $sp = strtolower((string) $s['SERVER_PROTOCOL']); $protocol = substr($sp, 0, strpos($sp, '/')) . (($ssl) ? 's' : ''); $port = $s['SERVER_PORT']; $port = ((!$ssl && $port == '80') || ($ssl && $port == '443')) ? '' : ':' . $port; - $host = ($use_forwarded_host && isset($s['HTTP_X_FORWARDED_HOST'])) ? $s['HTTP_X_FORWARDED_HOST'] : (isset($s['HTTP_HOST']) ? $s['HTTP_HOST'] : null); - $host = isset($host) ? $host : $s['SERVER_NAME'] . $port; + $host = ($use_forwarded_host && isset($s['HTTP_X_FORWARDED_HOST'])) ? $s['HTTP_X_FORWARDED_HOST'] : ($s['HTTP_HOST'] ?? null); + $host ??= $s['SERVER_NAME'] . $port; return $protocol . '://' . $host; } @@ -234,7 +236,6 @@ function full_url($s, $use_forwarded_host = false) } /** - * @param string $fcParams * @return string */ private function resolveCustomParameters(string $fcParams) diff --git a/Classes/Form/Element/StartNewElement.php b/Classes/Form/Element/StartNewElement.php index 5e2c46f..47ca0da 100644 --- a/Classes/Form/Element/StartNewElement.php +++ b/Classes/Form/Element/StartNewElement.php @@ -96,7 +96,7 @@ function getInputElem() } }); - '; diff --git a/Classes/Helper/FcHelper.php b/Classes/Helper/FcHelper.php index 622ce7b..8e63350 100644 --- a/Classes/Helper/FcHelper.php +++ b/Classes/Helper/FcHelper.php @@ -15,8 +15,8 @@ class FcHelper { - const CURL_OPTION_MAXREDIRS = 5; - const CURL_OPTION_USERAGENT = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)'; + public const CURL_OPTION_MAXREDIRS = 5; + public const CURL_OPTION_USERAGENT = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)'; /** * @var array @@ -168,13 +168,13 @@ public function getFormContent( $errorUrl = $siteerror; $sessionID = $GLOBALS['TSFE']->fe_user->id; - $GLOBALS['gFcUrl'] = trim($GLOBALS['gFcUrl'], " /\t\n\r\0\x0B"); + $GLOBALS['gFcUrl'] = trim((string) $GLOBALS['gFcUrl'], " /\t\n\r\0\x0B"); $GLOBALS['gFcUrl'] .= '/'; - if (preg_match('~&lang=([^&]+)~', $fcParams, $matches) === 1) { + if (preg_match('~&lang=([^&]+)~', (string) $fcParams, $matches) === 1) { $frontendLang = $matches[1]; - $fcParams = str_replace($matches[0], '', $fcParams); + $fcParams = str_replace($matches[0], '', (string) $fcParams); } return $GLOBALS['gFcUrl'] . 'form/provide/' . $projektId . diff --git a/Classes/Helper/WorkaroundHelper.php b/Classes/Helper/WorkaroundHelper.php index 6fadaa1..d2044cc 100644 --- a/Classes/Helper/WorkaroundHelper.php +++ b/Classes/Helper/WorkaroundHelper.php @@ -30,7 +30,7 @@ public function findFlexformDataByUid($uid) $query = $this->createQuery(); $query->statement('SELECT pi_flexform from tt_content where list_type="xmformcycle_xmformcycle" and uid = ' . $uid); $pages = $query->execute(true); - $xml = simplexml_load_string($pages[0]['pi_flexform']); + $xml = simplexml_load_string((string) $pages[0]['pi_flexform']); $flexformData = []; foreach ($xml->data->sheet as $sheet) { diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index e4ad5c9..423c53c 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -1,7 +1,8 @@ 'plugin', 'version' => '8.0.1', 'state' => 'stable', - 'uploadfolder' => '', - 'createDirs' => '', - 'clearcacheonload' => '', 'author' => 'XIMA MEDIA GmbH', 'author_email' => 'support@formcycle.de', 'author_company' => 'XIMA MEDIA GmbH', diff --git a/ext_localconf.php b/ext_localconf.php index c77a578..816c7a8 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,5 +1,5 @@ 10000000) { \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( - 'Xima.xm_formcycle', + 'XmFormcycle', 'Xmformcycle', [ \Xima\XmFormcycle\Controller\FormcycleController::class => 'list, formContent', @@ -17,10 +17,10 @@ ); } else { \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( - 'Xima.xm_formcycle', + 'XmFormcycle', 'Xmformcycle', [ - 'Formcycle' => 'list, formContent', + \Xima\XmFormcycle\Controller\FormcycleController::class => 'list, formContent', ], // non-cacheable actions $extConf['integrationMode'] == 'integrated' ? ['Formcycle' => 'list'] : [] diff --git a/ext_tables.php b/ext_tables.php index f8d5297..e16e468 100644 --- a/ext_tables.php +++ b/ext_tables.php @@ -1,4 +1,4 @@ withConfiguredRule(ExtEmConfRector::class, [ - ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [] + ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [], ]) ->withPaths([ - __DIR__ . '/Classes', + __DIR__ . '/ext_emconf.php', + __DIR__ . '/ext_localconf.php', + __DIR__ . '/ext_tables.php', + __DIR__ . '/Classes', __DIR__ . '/Configuration', __DIR__ . '/config', ]) @@ -27,7 +30,7 @@ ]) # To have a better analysis from PHPStan, we teach it here some more things ->withPHPStanConfigs([ - Typo3Option::PHPSTAN_FOR_RECTOR_PATH + Typo3Option::PHPSTAN_FOR_RECTOR_PATH, ]) ->withPhpVersion(PhpVersion::PHP_81) ->withRules([ @@ -43,6 +46,5 @@ 'ClassAliasMap.php', __DIR__ . '/**/Configuration/*.php', __DIR__ . '/**/Configuration/**/*.php', - ] - ]) -; + ], + ]); From fd22dd8e394d2422b1ef18ddaf474a6561ab98f8 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 14:24:13 +0100 Subject: [PATCH 05/66] feat: register custom tt_content element, custom form element --- Classes/Form/Element/FormcycleSelection.php | 16 +++++++ Configuration/TCA/Overrides/tt_content.php | 14 +++++- .../TCA/Overrides/tt_content_formcycle.php | 43 +++++++++++++++++++ Configuration/TypoScript/setup.typoscript | 5 +++ Resources/Private/Language/locallang.xlf | 16 +++++-- composer.json | 3 +- ext_localconf.php | 7 +++ ext_tables.sql | 3 ++ 8 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 Classes/Form/Element/FormcycleSelection.php create mode 100644 Configuration/TCA/Overrides/tt_content_formcycle.php create mode 100644 ext_tables.sql diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php new file mode 100644 index 0000000..b60fb15 --- /dev/null +++ b/Classes/Form/Element/FormcycleSelection.php @@ -0,0 +1,16 @@ +initializeResultArray(); + $resultArray['html'] = 'hello world'; + return $resultArray; + } +} diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php index a77c725..c618eaf 100644 --- a/Configuration/TCA/Overrides/tt_content.php +++ b/Configuration/TCA/Overrides/tt_content.php @@ -1,8 +1,9 @@ [ + 'label' => 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:tx_xmformcycle_form_id.label', + 'config' => [ + 'type' => 'formcycle-selection', + ], + ], +]; + +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('tt_content', $tempFields); diff --git a/Configuration/TCA/Overrides/tt_content_formcycle.php b/Configuration/TCA/Overrides/tt_content_formcycle.php new file mode 100644 index 0000000..2f7096a --- /dev/null +++ b/Configuration/TCA/Overrides/tt_content_formcycle.php @@ -0,0 +1,43 @@ + 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:palettes.formcycle.title', + 'showitem' => 'tx_xmformcycle_form_id', +]; + +$GLOBALS['TCA']['tt_content']['types']['formcycle'] = [ + 'showitem' => ' + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general, + --palette--;;general, + --palette--;;headers, + --div--;LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:formcycle, + --palette--;;formcycle, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, + --palette--;;frames, + --palette--;;appearanceLinks, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, + --palette--;;language, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, + --palette--;;hidden, + --palette--;;access, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, + categories, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes, + rowDescription, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended, + ', +]; diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index bc7999d..5ccdced 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -68,3 +68,8 @@ tx_xmformcycle_ajax { settings =< plugin.tx_xmformcycle.settings } } + +tt_content.formcycle =< lib.contentElement +tt_content.formcycle { + templateName = Formcycle +} diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index bc55d63..632854e 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -6,13 +6,23 @@ konrad.michalik@xima.de - + + Formcycle + + Formcycle - FormCycle + + Form + + + Formcycle + + + Form + XIMA® FormCycle forms in TYPO3 Frontend - XIMA® FormCycle forms in TYPO3 Frontend Formcycle diff --git a/composer.json b/composer.json index 24d9fba..ea7e80c 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "saschaegerer/phpstan-typo3": "^1.1.2", "ssch/typo3-rector": "^2.0", "symfony/translation": "^6.3", - "typo3/cms-base-distribution": "^12.4" + "typo3/cms-base-distribution": "*", + "typo3/cms-lowlevel": "*" }, "autoload": { "psr-4": { diff --git a/ext_localconf.php b/ext_localconf.php index 816c7a8..e1e9be0 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -76,3 +76,10 @@ } }' ); + +// register custom form element +$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1709039883] = [ + 'nodeName' => 'formcycle-selection', + 'priority' => 40, + 'class' => \Xima\XmFormcycle\Form\Element\FormcycleSelection::class +]; diff --git a/ext_tables.sql b/ext_tables.sql new file mode 100644 index 0000000..4fa1ff5 --- /dev/null +++ b/ext_tables.sql @@ -0,0 +1,3 @@ +create table tt_content ( + tx_xmformcycle_form_id varchar(255) default '' not null, +); From 2d5658b81d0f0c6e6bf7f6f0089330b676868ead Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 15:04:17 +0100 Subject: [PATCH 06/66] feat: add formcycle service and configuration classes --- Classes/Dto/FormcycleConfiguration.php | 42 +++++++++++++++++++ .../Error/FormcycleConfigurationException.php | 10 +++++ Classes/Form/Element/FormcycleSelection.php | 15 ++++++- Classes/Service/FormcycleService.php | 37 ++++++++++++++++ Configuration/Services.yaml | 11 +++++ .../Templates/Backend/FormcycleSelection.html | 3 ++ 6 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 Classes/Dto/FormcycleConfiguration.php create mode 100644 Classes/Error/FormcycleConfigurationException.php create mode 100644 Classes/Service/FormcycleService.php create mode 100644 Configuration/Services.yaml create mode 100644 Resources/Private/Templates/Backend/FormcycleSelection.html diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php new file mode 100644 index 0000000..ebed036 --- /dev/null +++ b/Classes/Dto/FormcycleConfiguration.php @@ -0,0 +1,42 @@ +formCycleUrl = rtrim($extConfiguration['formCycleUrl'] ?? '', '/'); + if (!$this->formCycleUrl || !GeneralUtility::isValidUrl($this->formCycleUrl)) { + throw new FormcycleConfigurationException('Invalid formCycleUrl "' . $this->formCycleUrl . '"', 1709041996); + } + + $this->formCycleFrontendUrl = $extConfiguration['formCycleFrontendUrl'] ?? ''; + $this->formCycleUser = $extConfiguration['formCycleUser'] ?? ''; + $this->formCyclePass = $extConfiguration['formCyclePass'] ?? ''; + $this->integrationMode = $extConfiguration['integrationMode'] ?? ''; + } + + public function getFormListUrl(): string + { + return sprintf('%s/plugin?name=FormList&xfc-rp-username=maik.schneider@xima.de&xfc-rp-password=pdn*UFZ3rvm0zec2ugd&xfc-rp-client=24871&format=json', $this->formCycleUrl); + } +} diff --git a/Classes/Error/FormcycleConfigurationException.php b/Classes/Error/FormcycleConfigurationException.php new file mode 100644 index 0000000..ceb0ab8 --- /dev/null +++ b/Classes/Error/FormcycleConfigurationException.php @@ -0,0 +1,10 @@ +getAvailableForms(); + + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:xm_formcycle/Resources/Private/Templates/Backend/FormcycleSelection.html'); + $view->assign('forms', $forms); + + $resultArray = $this->initializeResultArray(); - $resultArray['html'] = 'hello world'; + $resultArray['html'] = $view->render(); return $resultArray; } } diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php new file mode 100644 index 0000000..7ef7ca9 --- /dev/null +++ b/Classes/Service/FormcycleService.php @@ -0,0 +1,37 @@ +extensionConfiguration->get('xm_formcycle'); + $this->configuration = new FormcycleConfiguration($extConfig); + } catch (FormcycleConfigurationException $e) { + $this->error = $e->getMessage(); + } + } + + public function getAvailableForms(): array + { + if (!$this->configuration) { + return []; + } + + $jsonResponse = GeneralUtility::getUrl($this->configuration->getFormListUrl()); + + return json_decode($jsonResponse); + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 0000000..8e537a4 --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,11 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Xima\XmFormcycle\: + resource: '../Classes/*' + + Xima\XmFormcycle\Service\FormcycleService: + public: true diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html new file mode 100644 index 0000000..f971150 --- /dev/null +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -0,0 +1,3 @@ +{_all} + +fwe From 935393522937f82da1220f30d373cd7003f6b698 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 17:12:24 +0100 Subject: [PATCH 07/66] feat: register cache, fetch available forms --- Classes/Form/Element/FormcycleSelection.php | 2 +- Classes/Service/FormcycleService.php | 73 +++++++++++++++++++-- Configuration/Services.yaml | 7 ++ ext_localconf.php | 3 + 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index 931a344..fee1d71 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -15,7 +15,7 @@ public function render() { /** @var FormcycleService $fcService */ $fcService = GeneralUtility::makeInstance(FormcycleService::class); - $forms = $fcService->getAvailableForms(); + $forms = FormcycleService::groupForms($fcService->getAvailableForms()); $view = GeneralUtility::makeInstance(StandaloneView::class); $view->setTemplatePathAndFilename('EXT:xm_formcycle/Resources/Private/Templates/Backend/FormcycleSelection.html'); diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 7ef7ca9..c53ac31 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -2,9 +2,11 @@ namespace Xima\XmFormcycle\Service; +use finfo; +use JsonException; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Configuration\ConfigurationManager; use Xima\XmFormcycle\Dto\FormcycleConfiguration; use Xima\XmFormcycle\Error\FormcycleConfigurationException; @@ -14,8 +16,10 @@ final class FormcycleService private string $error = ''; - public function __construct(private readonly ExtensionConfiguration $extensionConfiguration) - { + public function __construct( + private readonly ExtensionConfiguration $extensionConfiguration, + private FrontendInterface $cache + ) { try { $extConfig = $this->extensionConfiguration->get('xm_formcycle'); $this->configuration = new FormcycleConfiguration($extConfig); @@ -24,14 +28,75 @@ public function __construct(private readonly ExtensionConfiguration $extensionCo } } + public static function groupForms(array $forms): array + { + $groupedForms = []; + foreach ($forms as $form) { + $index = $form['group'] ?? 0; + $groupedForms[$index] ??= []; + $groupedForms[$index][] = $form; + } + // sort "others" group (index=0) to end of array + uksort($groupedForms, static function ($a, $b) { + return $b === 0 ? -1 : $a > $b; + }); + return $groupedForms; + } + public function getAvailableForms(): array { if (!$this->configuration) { return []; } + $forms = $this->cache->get('availableForms'); + if ($forms === false) { + $forms = $this->loadAvailableForms(); + $this->cache->set('availableForms', $forms); + } + + return $forms; + } + + private function loadAvailableForms(): array + { + if (!$this->configuration) { + return []; + } + $jsonResponse = GeneralUtility::getUrl($this->configuration->getFormListUrl()); - return json_decode($jsonResponse); + if (!$jsonResponse) { + return []; + } + + try { + $forms = json_decode($jsonResponse, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + $this->error = 'Invalid JSON response of available forms endpoint'; + return []; + } + + if (!is_array($forms)) { + $this->error = 'Invalid JSON response of available forms endpoint'; + return []; + } + + self::encodePreviewImages($forms); + + return $forms; + } + + private static function encodePreviewImages(array &$forms): void + { + foreach ($forms as &$form) { + $thumbnail = $form['thumbnail'] ?? ''; + if ($thumbnail && GeneralUtility::isValidUrl($thumbnail)) { + $imageData = GeneralUtility::getUrl($thumbnail); + $fileInfo = new finfo(FILEINFO_MIME_TYPE); + $mimeType = $fileInfo->buffer($imageData); + $form['thumbnail'] = 'data:' . $mimeType . ';base64,' . base64_encode($imageData); + } + } } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 8e537a4..5baf8c5 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -7,5 +7,12 @@ services: Xima\XmFormcycle\: resource: '../Classes/*' + cache.xm_formcycle: + class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface + factory: [ '@TYPO3\CMS\Core\Cache\CacheManager', 'getCache' ] + arguments: [ 'xm_formcycle' ] + Xima\XmFormcycle\Service\FormcycleService: public: true + arguments: + $cache: '@cache.xm_formcycle' diff --git a/ext_localconf.php b/ext_localconf.php index e1e9be0..a5c659a 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -83,3 +83,6 @@ 'priority' => 40, 'class' => \Xima\XmFormcycle\Form\Element\FormcycleSelection::class ]; + +// register cache +$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['xm_formcycle'] ??= []; From 981b28d5be547c35b163b48669a47d29a8037932 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 17:14:08 +0100 Subject: [PATCH 08/66] feat: run phpfixer --- Classes/Controller/FormcycleController.php | 27 ++---- .../Error/FormcycleConfigurationException.php | 1 - Classes/Form/Element/FcElement.php | 3 +- Classes/Form/Element/FormcycleSelection.php | 3 - Classes/Form/Element/StartNewElement.php | 18 ++-- Classes/Form/Element/StartOpenElement.php | 4 +- Classes/Helper/FcHelper.php | 26 +++-- Classes/Helper/WorkaroundHelper.php | 4 +- Configuration/TCA/Overrides/sys_template.php | 3 +- Configuration/TCA/Overrides/tt_content.php | 2 +- composer.json | 6 +- ext_emconf.php | 3 - ext_localconf.php | 5 +- ext_tables.php | 5 +- php-cs-fixer.php | 96 +++++++++++++++++++ rector.php | 4 +- 16 files changed, 142 insertions(+), 68 deletions(-) create mode 100644 php-cs-fixer.php diff --git a/Classes/Controller/FormcycleController.php b/Classes/Controller/FormcycleController.php index 6265f73..9e80eed 100644 --- a/Classes/Controller/FormcycleController.php +++ b/Classes/Controller/FormcycleController.php @@ -35,13 +35,10 @@ /** * Class FormcycleController * - * @package xm_formcycle * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3 or later - * */ class FormcycleController extends ActionController { - /** * @var string */ @@ -52,9 +49,6 @@ class FormcycleController extends ActionController */ protected $extConf = []; - /** - * - */ public function initializeListAction() { $this->extConf = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$this->extKey]; @@ -62,8 +56,6 @@ public function initializeListAction() /** * action list - * - * @return void */ public function listAction(): ResponseInterface { @@ -125,9 +117,6 @@ public function initializeFormContentAction() ); } - /** - * - */ public function formContentAction(): ResponseInterface { $this->view->assignMultiple($this->getDirectly(true)); @@ -198,12 +187,12 @@ protected function getByAjax() * @param $uid * @return string */ - function getRedirectURL($uid) + public function getRedirectURL($uid) { return $this->uriBuilder ->reset() ->setArguments(['L' => GeneralUtility::makeInstance(Context::class)->getAspect('language')]) - ->setTargetPageUid(intval($uid)) + ->setTargetPageUid((int)$uid) ->setCreateAbsoluteUri(true) ->build(); } @@ -213,10 +202,10 @@ function getRedirectURL($uid) * @param bool|false $use_forwarded_host * @return string */ - function url_origin($s, $use_forwarded_host = false) + public function url_origin($s, $use_forwarded_host = false) { $ssl = (!empty($s['HTTPS']) && $s['HTTPS'] == 'on') ? true : false; - $sp = strtolower((string) $s['SERVER_PROTOCOL']); + $sp = strtolower((string)$s['SERVER_PROTOCOL']); $protocol = substr($sp, 0, strpos($sp, '/')) . (($ssl) ? 's' : ''); $port = $s['SERVER_PORT']; $port = ((!$ssl && $port == '80') || ($ssl && $port == '443')) ? '' : ':' . $port; @@ -230,7 +219,7 @@ function url_origin($s, $use_forwarded_host = false) * @param bool|false $use_forwarded_host * @return string */ - function full_url($s, $use_forwarded_host = false) + public function full_url($s, $use_forwarded_host = false) { return $this->url_origin($s, $use_forwarded_host) . strtok($s['REQUEST_URI'], '?'); } @@ -243,16 +232,14 @@ private function resolveCustomParameters(string $fcParams) $result = $fcParams; if (preg_match_all('~(?:{|%7B)(?[\d\w]+)(?:}|%7D)~', $fcParams, $matches) !== false) { - foreach ($matches['params'] as $idx => $param) { if (array_key_exists($param, $_GET)) { $result = str_replace( $matches[0][$idx], - strip_tags(strval($_GET[$param])), + strip_tags((string)($_GET[$param])), $result ); - } - else { + } else { $result = str_replace( $matches[0][$idx], '', diff --git a/Classes/Error/FormcycleConfigurationException.php b/Classes/Error/FormcycleConfigurationException.php index ceb0ab8..fac9cbc 100644 --- a/Classes/Error/FormcycleConfigurationException.php +++ b/Classes/Error/FormcycleConfigurationException.php @@ -6,5 +6,4 @@ class FormcycleConfigurationException extends Exception { - } diff --git a/Classes/Form/Element/FcElement.php b/Classes/Form/Element/FcElement.php index 1eee0b6..e764685 100644 --- a/Classes/Form/Element/FcElement.php +++ b/Classes/Form/Element/FcElement.php @@ -1,4 +1,5 @@ data, for example the above @@ -31,5 +31,4 @@ public function render() $result['html'] = '  ' . $message_link . '
  ' . $message . '

'; return $result; } - } diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index fee1d71..c735a69 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -3,14 +3,12 @@ namespace Xima\XmFormcycle\Form\Element; use TYPO3\CMS\Backend\Form\Element\AbstractFormElement; -use TYPO3\CMS\Backend\Form\NodeFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Fluid\View\StandaloneView; use Xima\XmFormcycle\Service\FormcycleService; class FormcycleSelection extends AbstractFormElement { - public function render() { /** @var FormcycleService $fcService */ @@ -21,7 +19,6 @@ public function render() $view->setTemplatePathAndFilename('EXT:xm_formcycle/Resources/Private/Templates/Backend/FormcycleSelection.html'); $view->assign('forms', $forms); - $resultArray = $this->initializeResultArray(); $resultArray['html'] = $view->render(); return $resultArray; diff --git a/Classes/Form/Element/StartNewElement.php b/Classes/Form/Element/StartNewElement.php index 47ca0da..d9d0de7 100644 --- a/Classes/Form/Element/StartNewElement.php +++ b/Classes/Form/Element/StartNewElement.php @@ -1,15 +1,15 @@ data, for example the above @@ -27,8 +27,10 @@ public function render() $selProjectId = $this->settings['xf']['xfc_p_id']; - if (is_array($PA['databaseRow']['pi_flexform']) && array_key_exists('data', - $PA['databaseRow']['pi_flexform'])) { + if (is_array($PA['databaseRow']['pi_flexform']) && array_key_exists( + 'data', + $PA['databaseRow']['pi_flexform'] + )) { $pid = $PA['databaseRow']['pi_flexform']['data']['sheetGeneralOptions']['lDEF']['settings.xf.xfc_p_id']['vDEF']; } else { $xml = new SimpleXMLElement($PA['databaseRow']['pi_flexform']); @@ -96,13 +98,13 @@ function getInputElem() } }); - '; - + '; $result = $this->initializeResultArray(); $result['html'] = $retTemp; return $result; } - } diff --git a/Classes/Form/Element/StartOpenElement.php b/Classes/Form/Element/StartOpenElement.php index a6b0b55..37b33ea 100644 --- a/Classes/Form/Element/StartOpenElement.php +++ b/Classes/Form/Element/StartOpenElement.php @@ -1,4 +1,5 @@ data, for example the above @@ -20,7 +20,6 @@ public function render() $message_link = $mssage_link_style . 'FormCycle TYPO3 extension help'; if ($user_lang == 'de') { - $message_link = $mssage_link_style . 'Hilfe für FormCycle TYPO3 Erweiterung öffnen'; } $fc_HelpUrl = 'http://help.formcycle.eu/xwiki/bin/view/CMS+Extension/Typo3+Extension'; @@ -29,5 +28,4 @@ public function render() $result['html'] = '  ' . $message_link . '
'; return $result; } - } diff --git a/Classes/Helper/FcHelper.php b/Classes/Helper/FcHelper.php index 8e63350..96d722e 100644 --- a/Classes/Helper/FcHelper.php +++ b/Classes/Helper/FcHelper.php @@ -1,20 +1,18 @@ fe_user->id; - $GLOBALS['gFcUrl'] = trim((string) $GLOBALS['gFcUrl'], " /\t\n\r\0\x0B"); + $GLOBALS['gFcUrl'] = trim((string)$GLOBALS['gFcUrl'], " /\t\n\r\0\x0B"); $GLOBALS['gFcUrl'] .= '/'; - if (preg_match('~&lang=([^&]+)~', (string) $fcParams, $matches) === 1) { - + if (preg_match('~&lang=([^&]+)~', (string)$fcParams, $matches) === 1) { $frontendLang = $matches[1]; - $fcParams = str_replace($matches[0], '', (string) $fcParams); + $fcParams = str_replace($matches[0], '', (string)$fcParams); } return $GLOBALS['gFcUrl'] . 'form/provide/' . $projektId . @@ -204,10 +203,7 @@ public function getFcIframeUrl() */ public function getFcAdministrationUrl() { - return $GLOBALS['gFcUrl']; } - } - } diff --git a/Classes/Helper/WorkaroundHelper.php b/Classes/Helper/WorkaroundHelper.php index d2044cc..7d1046e 100644 --- a/Classes/Helper/WorkaroundHelper.php +++ b/Classes/Helper/WorkaroundHelper.php @@ -11,7 +11,6 @@ */ class WorkaroundHelper extends Repository { - /** * Find Flexform settings by UID. * @@ -30,7 +29,7 @@ public function findFlexformDataByUid($uid) $query = $this->createQuery(); $query->statement('SELECT pi_flexform from tt_content where list_type="xmformcycle_xmformcycle" and uid = ' . $uid); $pages = $query->execute(true); - $xml = simplexml_load_string((string) $pages[0]['pi_flexform']); + $xml = simplexml_load_string((string)$pages[0]['pi_flexform']); $flexformData = []; foreach ($xml->data->sheet as $sheet) { @@ -45,5 +44,4 @@ public function findFlexformDataByUid($uid) return $flexformData; } - } diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index 423c53c..ede8f41 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -1,5 +1,7 @@ [], ], ]; - - - diff --git a/ext_localconf.php b/ext_localconf.php index a5c659a..710fdff 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,6 +1,7 @@ 'formcycle-selection', 'priority' => 40, - 'class' => \Xima\XmFormcycle\Form\Element\FormcycleSelection::class + 'class' => \Xima\XmFormcycle\Form\Element\FormcycleSelection::class, ]; // register cache diff --git a/ext_tables.php b/ext_tables.php index e16e468..01be305 100644 --- a/ext_tables.php +++ b/ext_tables.php @@ -1,4 +1,5 @@ ignoreVCSIgnored(true) + ->in(realpath(__DIR__)); +// Return a Code Sniffing configuration using +// all sniffers needed for PSR-2 +// and additionally: +// - Remove leading slashes in use clauses. +// - PHP single-line arrays should not have trailing comma. +// - Single-line whitespace before closing semicolon are prohibited. +// - Remove unused use statements in the PHP source code +// - Ensure Concatenation to have at least one whitespace around +// - Remove trailing whitespace at the end of blank lines. +return (new \PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_after_opening_tag' => true, + 'braces' => ['allow_single_line_closure' => true], + 'cast_spaces' => ['space' => 'none'], + 'compact_nullable_typehint' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'dir_constant' => true, + 'function_typehint_space' => true, + 'lowercase_cast' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'modernize_types_casting' => true, + 'native_function_casing' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_null_property_initialization' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_superfluous_elseif' => true, + 'no_trailing_comma_in_singleline' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']], + 'php_unit_mock_short_will_return' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'return_type_declaration' => ['space_before' => 'none'], + 'single_quote' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], + 'single_trait_insert_per_statement' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'whitespace_after_comma_in_array' => true, + ]) + ->setFinder($finder); diff --git a/rector.php b/rector.php index 703fb1d..81b7181 100644 --- a/rector.php +++ b/rector.php @@ -28,7 +28,7 @@ ->withSets([ Typo3LevelSetList::UP_TO_TYPO3_12, ]) - # To have a better analysis from PHPStan, we teach it here some more things + // To have a better analysis from PHPStan, we teach it here some more things ->withPHPStanConfigs([ Typo3Option::PHPSTAN_FOR_RECTOR_PATH, ]) @@ -36,7 +36,7 @@ ->withRules([ ConvertImplicitVariablesToExplicitGlobalsRector::class, ]) - # If you use importNames(), you should consider excluding some TYPO3 files. + // If you use importNames(), you should consider excluding some TYPO3 files. ->withSkip([ // @see https://github.com/sabbelasichon/typo3-rector/issues/2536 __DIR__ . '/**/Configuration/ExtensionBuilder/*', From 48d779203e3a365df50882ede0acf88dbdc1883a Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 18:06:39 +0100 Subject: [PATCH 09/66] feat: add unit tests for FormcycleConfiguration --- Classes/Dto/FormcycleConfiguration.php | 53 ++++++++++++++--- Classes/Service/FormcycleService.php | 2 +- Tests/Unit/Dto/FormcycleConfigurationTest.php | 59 +++++++++++++++++++ Tests/Unit/Service/FormcycleServiceTest.php | 48 +++++++++++++++ composer.json | 8 ++- phpunit.unit.xml | 18 ++++++ 6 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 Tests/Unit/Dto/FormcycleConfigurationTest.php create mode 100644 Tests/Unit/Service/FormcycleServiceTest.php create mode 100644 phpunit.unit.xml diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php index ebed036..ba31e1d 100644 --- a/Classes/Dto/FormcycleConfiguration.php +++ b/Classes/Dto/FormcycleConfiguration.php @@ -7,6 +7,10 @@ final class FormcycleConfiguration { + public const INTEGRATION_MODE_INTEGRATED = 'integrated'; + public const INTEGRATION_MODE_IFRAME = 'iFrame'; + public const INTEGRATION_MODE_AJAX = 'AJAX'; + private string $formCycleUrl; private string $formCycleFrontendUrl; @@ -22,21 +26,52 @@ final class FormcycleConfiguration /** * @throws FormcycleConfigurationException */ - public function __construct(array $extConfiguration) + public static function createFromExtensionConfiguration(array $extConfiguration): self { - $this->formCycleUrl = rtrim($extConfiguration['formCycleUrl'] ?? '', '/'); - if (!$this->formCycleUrl || !GeneralUtility::isValidUrl($this->formCycleUrl)) { - throw new FormcycleConfigurationException('Invalid formCycleUrl "' . $this->formCycleUrl . '"', 1709041996); + $config = new self(); + $config->formCycleUrl = rtrim($extConfiguration['formCycleUrl'] ?? '', '/'); + if (!$config->formCycleUrl || !GeneralUtility::isValidUrl($config->formCycleUrl)) { + throw new FormcycleConfigurationException( + 'Invalid formCycleUrl "' . $config->formCycleUrl . '"', + 1709041996 + ); + } + + $config->formCycleUser = $extConfiguration['formCycleUser'] ?? ''; + $config->formCyclePass = $extConfiguration['formCyclePass'] ?? ''; + if (!$config->formCycleUser || !$config->formCyclePass) { + throw new FormcycleConfigurationException('No formCycleUser or formCyclePass set', 1709052037); + } + + $mode = $extConfiguration['integrationMode'] ?? ''; + $config->integrationMode = $mode ?: self::INTEGRATION_MODE_INTEGRATED; + if (!in_array( + $config->integrationMode, + [self::INTEGRATION_MODE_IFRAME, self::INTEGRATION_MODE_INTEGRATED, self::INTEGRATION_MODE_AJAX], + true + )) { + throw new FormcycleConfigurationException( + 'Invalid integration mode "' . $config->integrationMode . '"', + 1709052040 + ); + } + + $config->formCycleFrontendUrl = $extConfiguration['formCycleFrontendUrl'] ?? ''; + if ($config->formCycleFrontendUrl && !GeneralUtility::isValidUrl($config->formCycleFrontendUrl)) { + throw new FormcycleConfigurationException( + 'Invalid formCycleFrontendUrl "' . $config->formCycleFrontendUrl . '"', + 1709052152 + ); } - $this->formCycleFrontendUrl = $extConfiguration['formCycleFrontendUrl'] ?? ''; - $this->formCycleUser = $extConfiguration['formCycleUser'] ?? ''; - $this->formCyclePass = $extConfiguration['formCyclePass'] ?? ''; - $this->integrationMode = $extConfiguration['integrationMode'] ?? ''; + return $config; } public function getFormListUrl(): string { - return sprintf('%s/plugin?name=FormList&xfc-rp-username=maik.schneider@xima.de&xfc-rp-password=pdn*UFZ3rvm0zec2ugd&xfc-rp-client=24871&format=json', $this->formCycleUrl); + return sprintf( + '%s/plugin?name=FormList&xfc-rp-username=maik.schneider@xima.de&xfc-rp-password=pdn*UFZ3rvm0zec2ugd&xfc-rp-client=24871&format=json', + $this->formCycleUrl + ); } } diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index c53ac31..958df87 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -22,7 +22,7 @@ public function __construct( ) { try { $extConfig = $this->extensionConfiguration->get('xm_formcycle'); - $this->configuration = new FormcycleConfiguration($extConfig); + $this->configuration = FormcycleConfiguration::createFromExtensionConfiguration($extConfig); } catch (FormcycleConfigurationException $e) { $this->error = $e->getMessage(); } diff --git a/Tests/Unit/Dto/FormcycleConfigurationTest.php b/Tests/Unit/Dto/FormcycleConfigurationTest.php new file mode 100644 index 0000000..7e0eb5e --- /dev/null +++ b/Tests/Unit/Dto/FormcycleConfigurationTest.php @@ -0,0 +1,59 @@ + 'https://example.com', + 'formCycleFrontendUrl' => '', + 'formCycleUser' => 'username', + 'formCyclePass' => 'password', + 'integrationMode' => '', + ]; + + public function testCreateFromExtensionConfiguration(): void + { + $this->expectException(FormcycleConfigurationException::class); + FormcycleConfiguration::createFromExtensionConfiguration([]); + } + + public function testInvalidFormcycleUrl(): void + { + $this->expectException(FormcycleConfigurationException::class); + $this->validExtensionConfiguration['formCycleUrl'] = 'x'; + FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + } + + public function testInvalidFormcycleFrontendUrl(): void + { + $this->expectException(FormcycleConfigurationException::class); + $this->validExtensionConfiguration['formCycleFrontendUrl'] = 'x'; + FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + } + + public function testMissingUsername(): void + { + $this->expectException(FormcycleConfigurationException::class); + $this->validExtensionConfiguration['formCycleUser'] = ''; + FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + } + + public function testMissingPassword(): void + { + $this->expectException(FormcycleConfigurationException::class); + $this->validExtensionConfiguration['formCyclePass'] = ''; + FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + } + + public function testInvalidIntegrationMode(): void + { + $this->expectException(FormcycleConfigurationException::class); + $this->validExtensionConfiguration['integrationMode'] = 'ajax'; + FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + } +} diff --git a/Tests/Unit/Service/FormcycleServiceTest.php b/Tests/Unit/Service/FormcycleServiceTest.php new file mode 100644 index 0000000..797f5dd --- /dev/null +++ b/Tests/Unit/Service/FormcycleServiceTest.php @@ -0,0 +1,48 @@ + 'b', + 'name' => 'Foo', + ], + [ + 'name' => 'Bar', + ], + [ + 'group' => 'a', + 'name' => 'Hossa', + ], + ]; + + $result = FormcycleService::groupForms($arr); + + self::assertEquals([ + 'a' => [ + [ + 'group' => 'a', + 'name' => 'Hossa', + ], + ], + 'b' => [ + [ + 'group' => 'b', + 'name' => 'Foo', + ], + ], + 0 => [ + [ + 'name' => 'Bar', + ], + ], + ], $result); + } +} diff --git a/composer.json b/composer.json index 199f379..4052fa0 100644 --- a/composer.json +++ b/composer.json @@ -39,10 +39,16 @@ "Xima\\XmFormcycle\\": "Classes/" } }, + "autoload-dev": { + "psr-4": { + "Xima\\XmFormcycle\\Tests\\": "Tests/" + } + }, "scripts": { "php:fixer": "php vendor/bin/php-cs-fixer --config=php-cs-fixer.php fix", "php:stan": "php vendor/bin/phpstan --generate-baseline=phpstan-baseline.neon --allow-empty-baseline", - "typoscript:lint": "php vendor/bin/typoscript-lint" + "typoscript:lint": "php vendor/bin/typoscript-lint", + "test:unit": "php vendor/bin/phpunit -c phpunit.unit.xml" }, "config": { "sort-packages": true, diff --git a/phpunit.unit.xml b/phpunit.unit.xml new file mode 100644 index 0000000..121958e --- /dev/null +++ b/phpunit.unit.xml @@ -0,0 +1,18 @@ + + + + + Tests/Unit + + + + + Classes + + + From 5ff12f88c720b9fd37bdaf5ccd35fa411ba8a644 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 18:33:01 +0100 Subject: [PATCH 10/66] feat: add error output --- Classes/Form/Element/FormcycleSelection.php | 2 ++ Classes/Service/FormcycleService.php | 9 +++++++-- Resources/Private/Language/locallang.xlf | 15 +++++++++++++++ .../Templates/Backend/FormcycleSelection.html | 4 +++- Tests/Unit/Dto/FormcycleConfigurationTest.php | 5 +++++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index c735a69..974d357 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -14,10 +14,12 @@ public function render() /** @var FormcycleService $fcService */ $fcService = GeneralUtility::makeInstance(FormcycleService::class); $forms = FormcycleService::groupForms($fcService->getAvailableForms()); + $errorCode = $fcService->getErrorCode(); $view = GeneralUtility::makeInstance(StandaloneView::class); $view->setTemplatePathAndFilename('EXT:xm_formcycle/Resources/Private/Templates/Backend/FormcycleSelection.html'); $view->assign('forms', $forms); + $view->assign('errorCode', $errorCode); $resultArray = $this->initializeResultArray(); $resultArray['html'] = $view->render(); diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 958df87..06b9145 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -14,7 +14,7 @@ final class FormcycleService { private ?FormcycleConfiguration $configuration = null; - private string $error = ''; + private int $errorCode = 0; public function __construct( private readonly ExtensionConfiguration $extensionConfiguration, @@ -24,7 +24,7 @@ public function __construct( $extConfig = $this->extensionConfiguration->get('xm_formcycle'); $this->configuration = FormcycleConfiguration::createFromExtensionConfiguration($extConfig); } catch (FormcycleConfigurationException $e) { - $this->error = $e->getMessage(); + $this->errorCode = (int)$e->getCode(); } } @@ -99,4 +99,9 @@ private static function encodePreviewImages(array &$forms): void } } } + + public function getErrorCode(): int + { + return $this->errorCode; + } } diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 632854e..c43bf00 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -21,6 +21,21 @@ Form + + Configuration error + + + The extension configuration "formCycleUrl" is missing or invalid + + + The extension configuration "formCycleUser" or "formCyclePass" is missing + + + The extension configuration "integrationMode" is invalid. Valid options are: "integrated", "iFrame" or "AJAX". + + + The extension configuration "formCycleFrontendUrl" is invalid + XIMA® FormCycle forms in TYPO3 Frontend diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index f971150..f79b71b 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -1,3 +1,5 @@ {_all} -fwe + + {f:translate(key: 'error.{errorCode}.message', extensionName:'XmFormcycle')} + diff --git a/Tests/Unit/Dto/FormcycleConfigurationTest.php b/Tests/Unit/Dto/FormcycleConfigurationTest.php index 7e0eb5e..39722f2 100644 --- a/Tests/Unit/Dto/FormcycleConfigurationTest.php +++ b/Tests/Unit/Dto/FormcycleConfigurationTest.php @@ -25,6 +25,7 @@ public function testCreateFromExtensionConfiguration(): void public function testInvalidFormcycleUrl(): void { $this->expectException(FormcycleConfigurationException::class); + $this->expectExceptionCode(1709041996); $this->validExtensionConfiguration['formCycleUrl'] = 'x'; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } @@ -32,6 +33,7 @@ public function testInvalidFormcycleUrl(): void public function testInvalidFormcycleFrontendUrl(): void { $this->expectException(FormcycleConfigurationException::class); + $this->expectExceptionCode(1709052152); $this->validExtensionConfiguration['formCycleFrontendUrl'] = 'x'; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } @@ -39,6 +41,7 @@ public function testInvalidFormcycleFrontendUrl(): void public function testMissingUsername(): void { $this->expectException(FormcycleConfigurationException::class); + $this->expectExceptionCode(1709052037); $this->validExtensionConfiguration['formCycleUser'] = ''; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } @@ -46,6 +49,7 @@ public function testMissingUsername(): void public function testMissingPassword(): void { $this->expectException(FormcycleConfigurationException::class); + $this->expectExceptionCode(1709052037); $this->validExtensionConfiguration['formCyclePass'] = ''; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } @@ -53,6 +57,7 @@ public function testMissingPassword(): void public function testInvalidIntegrationMode(): void { $this->expectException(FormcycleConfigurationException::class); + $this->expectExceptionCode(1709052040); $this->validExtensionConfiguration['integrationMode'] = 'ajax'; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } From f8fc7d14c55a231355f6a71643cca61d063f5f6a Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 18:53:22 +0100 Subject: [PATCH 11/66] feat: add simple form selection output --- Classes/Dto/FormcycleConfiguration.php | 7 ++++-- Resources/Private/Language/locallang.xlf | 3 +++ .../Templates/Backend/FormcycleSelection.html | 24 ++++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php index ba31e1d..4eb1ecc 100644 --- a/Classes/Dto/FormcycleConfiguration.php +++ b/Classes/Dto/FormcycleConfiguration.php @@ -70,8 +70,11 @@ public static function createFromExtensionConfiguration(array $extConfiguration) public function getFormListUrl(): string { return sprintf( - '%s/plugin?name=FormList&xfc-rp-username=maik.schneider@xima.de&xfc-rp-password=pdn*UFZ3rvm0zec2ugd&xfc-rp-client=24871&format=json', - $this->formCycleUrl + '%s/plugin?name=FormList&xfc-rp-username=%s&xfc-rp-password=%s&xfc-rp-client=%s&format=json', + $this->formCycleUrl, + $this->formCycleUser, + $this->formCyclePass, + $this->client, ); } } diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index c43bf00..a09b275 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -36,6 +36,9 @@ The extension configuration "formCycleFrontendUrl" is invalid + + Ungrouped + XIMA® FormCycle forms in TYPO3 Frontend diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index f79b71b..129d3bf 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -1,5 +1,27 @@ {_all} - {f:translate(key: 'error.{errorCode}.message', extensionName:'XmFormcycle')} + + {f:translate(key: 'error.{errorCode}.message', extensionName:'XmFormcycle')} + + + +
+

+ {groupName} +

+
+ +
+
From a6c806a3fd1efc570043cb6bb9fe2de8ad956371 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 27 Feb 2024 18:57:42 +0100 Subject: [PATCH 12/66] feat: some style --- .../Private/Templates/Backend/FormcycleSelection.html | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index 129d3bf..d53cf91 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -1,5 +1,3 @@ -{_all} - {f:translate(key: 'error.{errorCode}.message', extensionName:'XmFormcycle')} @@ -15,13 +13,14 @@

From 9fd4cc077eefc6cda944471ec3ec5df8abd99e24 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 07:55:37 +0100 Subject: [PATCH 13/66] feat: add FormcycleConnectionException --- .../Error/FormcycleConnectionException.php | 9 ++++ Classes/Form/Element/FormcycleSelection.php | 25 +++++++--- Classes/Service/FormcycleService.php | 49 ++++++++----------- Resources/Private/Language/locallang.xlf | 6 +++ .../Templates/Backend/FormcycleSelection.html | 2 +- Tests/Unit/Service/FormcycleServiceTest.php | 2 + 6 files changed, 57 insertions(+), 36 deletions(-) create mode 100644 Classes/Error/FormcycleConnectionException.php diff --git a/Classes/Error/FormcycleConnectionException.php b/Classes/Error/FormcycleConnectionException.php new file mode 100644 index 0000000..f0ca9ff --- /dev/null +++ b/Classes/Error/FormcycleConnectionException.php @@ -0,0 +1,9 @@ +getAvailableForms()); - $errorCode = $fcService->getErrorCode(); - $view = GeneralUtility::makeInstance(StandaloneView::class); $view->setTemplatePathAndFilename('EXT:xm_formcycle/Resources/Private/Templates/Backend/FormcycleSelection.html'); - $view->assign('forms', $forms); - $view->assign('errorCode', $errorCode); + + try { + /** @var FormcycleService $fcService */ + $fcService = GeneralUtility::makeInstance(FormcycleService::class); + $forms = $fcService->getAvailableForms(); + $forms = FormcycleService::groupForms($forms); + $view->assign('forms', $forms); + } catch (FormcycleConfigurationException $e) { + $errorCode = $e->getCode(); + $view->assign('errorCode', $errorCode); + $view->assign('errorType', 'configuration'); + } catch (FormcycleConnectionException $e) { + $errorCode = $e->getCode(); + $view->assign('errorCode', $errorCode); + $view->assign('errorType', 'connection'); + } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $view->render(); diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 06b9145..7c42187 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -5,27 +5,29 @@ use finfo; use JsonException; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException; +use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Utility\GeneralUtility; use Xima\XmFormcycle\Dto\FormcycleConfiguration; use Xima\XmFormcycle\Error\FormcycleConfigurationException; +use Xima\XmFormcycle\Error\FormcycleConnectionException; final class FormcycleService { - private ?FormcycleConfiguration $configuration = null; - - private int $errorCode = 0; + private ?FormcycleConfiguration $configuration; + /** + * @throws ExtensionConfigurationPathDoesNotExistException + * @throws ExtensionConfigurationExtensionNotConfiguredException + * @throws FormcycleConfigurationException + */ public function __construct( private readonly ExtensionConfiguration $extensionConfiguration, - private FrontendInterface $cache + private readonly FrontendInterface $cache ) { - try { - $extConfig = $this->extensionConfiguration->get('xm_formcycle'); - $this->configuration = FormcycleConfiguration::createFromExtensionConfiguration($extConfig); - } catch (FormcycleConfigurationException $e) { - $this->errorCode = (int)$e->getCode(); - } + $extConfig = $this->extensionConfiguration->get('xm_formcycle'); + $this->configuration = FormcycleConfiguration::createFromExtensionConfiguration($extConfig); } public static function groupForms(array $forms): array @@ -43,12 +45,11 @@ public static function groupForms(array $forms): array return $groupedForms; } + /** + * @throws FormcycleConnectionException + */ public function getAvailableForms(): array { - if (!$this->configuration) { - return []; - } - $forms = $this->cache->get('availableForms'); if ($forms === false) { $forms = $this->loadAvailableForms(); @@ -58,28 +59,25 @@ public function getAvailableForms(): array return $forms; } + /** + * @throws FormcycleConnectionException + */ private function loadAvailableForms(): array { - if (!$this->configuration) { - return []; - } - $jsonResponse = GeneralUtility::getUrl($this->configuration->getFormListUrl()); if (!$jsonResponse) { - return []; + throw new FormcycleConnectionException('Loading available forms: No response of endpoint', 1709102526); } try { $forms = json_decode($jsonResponse, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { - $this->error = 'Invalid JSON response of available forms endpoint'; - return []; + throw new FormcycleConnectionException('Loading available forms: Invalid JSON response of endpoint', 1709102526); } if (!is_array($forms)) { - $this->error = 'Invalid JSON response of available forms endpoint'; - return []; + throw new FormcycleConnectionException('Loading available forms: Invalid JSON response of endpoint', 1709102526); } self::encodePreviewImages($forms); @@ -99,9 +97,4 @@ private static function encodePreviewImages(array &$forms): void } } } - - public function getErrorCode(): int - { - return $this->errorCode; - } } diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index a09b275..703d7bb 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -24,6 +24,12 @@ Configuration error + + Connection error + + + No response from the endpoint when attempting to fetch available Formcycle forms + The extension configuration "formCycleUrl" is missing or invalid diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index d53cf91..987f37e 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -1,5 +1,5 @@ - + {f:translate(key: 'error.{errorCode}.message', extensionName:'XmFormcycle')} diff --git a/Tests/Unit/Service/FormcycleServiceTest.php b/Tests/Unit/Service/FormcycleServiceTest.php index 797f5dd..f59ad45 100644 --- a/Tests/Unit/Service/FormcycleServiceTest.php +++ b/Tests/Unit/Service/FormcycleServiceTest.php @@ -9,6 +9,8 @@ class FormcycleServiceTest extends UnitTestCase { public function testGroupForms(): void { + self::assertEmpty(FormcycleService::groupForms([])); + $arr = [ [ 'group' => 'b', From 4d8739996bec693b92c9ebde2a286248c3d605ba Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 08:00:55 +0100 Subject: [PATCH 14/66] feat: move grouping of forms into selection element --- Classes/Form/Element/FormcycleSelection.php | 17 ++++++- Classes/Service/FormcycleService.php | 15 ------ .../Form/Element/FormcycleSelectionTest.php | 50 +++++++++++++++++++ Tests/Unit/Service/FormcycleServiceTest.php | 38 -------------- 4 files changed, 66 insertions(+), 54 deletions(-) create mode 100644 Tests/Unit/Form/Element/FormcycleSelectionTest.php diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index 770e68f..00c902b 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -20,7 +20,7 @@ public function render() /** @var FormcycleService $fcService */ $fcService = GeneralUtility::makeInstance(FormcycleService::class); $forms = $fcService->getAvailableForms(); - $forms = FormcycleService::groupForms($forms); + $forms = self::groupForms($forms); $view->assign('forms', $forms); } catch (FormcycleConfigurationException $e) { $errorCode = $e->getCode(); @@ -36,4 +36,19 @@ public function render() $resultArray['html'] = $view->render(); return $resultArray; } + + public static function groupForms(array $forms): array + { + $groupedForms = []; + foreach ($forms as $form) { + $index = $form['group'] ?? 0; + $groupedForms[$index] ??= []; + $groupedForms[$index][] = $form; + } + // sort "others" group (index=0) to end of array + uksort($groupedForms, static function ($a, $b) { + return $b === 0 ? -1 : $a > $b; + }); + return $groupedForms; + } } diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 7c42187..56b4bec 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -30,21 +30,6 @@ public function __construct( $this->configuration = FormcycleConfiguration::createFromExtensionConfiguration($extConfig); } - public static function groupForms(array $forms): array - { - $groupedForms = []; - foreach ($forms as $form) { - $index = $form['group'] ?? 0; - $groupedForms[$index] ??= []; - $groupedForms[$index][] = $form; - } - // sort "others" group (index=0) to end of array - uksort($groupedForms, static function ($a, $b) { - return $b === 0 ? -1 : $a > $b; - }); - return $groupedForms; - } - /** * @throws FormcycleConnectionException */ diff --git a/Tests/Unit/Form/Element/FormcycleSelectionTest.php b/Tests/Unit/Form/Element/FormcycleSelectionTest.php new file mode 100644 index 0000000..a12f09c --- /dev/null +++ b/Tests/Unit/Form/Element/FormcycleSelectionTest.php @@ -0,0 +1,50 @@ + 'b', + 'name' => 'Foo', + ], + [ + 'name' => 'Bar', + ], + [ + 'group' => 'a', + 'name' => 'Hossa', + ], + ]; + + $result = FormcycleSelection::groupForms($arr); + + self::assertEquals([ + 'a' => [ + [ + 'group' => 'a', + 'name' => 'Hossa', + ], + ], + 'b' => [ + [ + 'group' => 'b', + 'name' => 'Foo', + ], + ], + 0 => [ + [ + 'name' => 'Bar', + ], + ], + ], $result); + } +} diff --git a/Tests/Unit/Service/FormcycleServiceTest.php b/Tests/Unit/Service/FormcycleServiceTest.php index f59ad45..1716c21 100644 --- a/Tests/Unit/Service/FormcycleServiceTest.php +++ b/Tests/Unit/Service/FormcycleServiceTest.php @@ -3,48 +3,10 @@ namespace Xima\XmFormcycle\Tests\Unit\Service; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; -use Xima\XmFormcycle\Service\FormcycleService; class FormcycleServiceTest extends UnitTestCase { public function testGroupForms(): void { - self::assertEmpty(FormcycleService::groupForms([])); - - $arr = [ - [ - 'group' => 'b', - 'name' => 'Foo', - ], - [ - 'name' => 'Bar', - ], - [ - 'group' => 'a', - 'name' => 'Hossa', - ], - ]; - - $result = FormcycleService::groupForms($arr); - - self::assertEquals([ - 'a' => [ - [ - 'group' => 'a', - 'name' => 'Hossa', - ], - ], - 'b' => [ - [ - 'group' => 'b', - 'name' => 'Foo', - ], - ], - 0 => [ - [ - 'name' => 'Bar', - ], - ], - ], $result); } } From 74f23e361da8442c1c147b14415f21c9b043816d Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 10:06:08 +0100 Subject: [PATCH 15/66] feat: add javascript and ajax controller --- Classes/Controller/BackendController.php | 45 +++++++++++++++++ Classes/Form/Element/FormcycleSelection.php | 19 +++++-- Classes/Service/FormcycleService.php | 23 +++++++-- Configuration/Backend/AjaxRoutes.php | 14 ++++++ Configuration/JavaScriptModules.php | 14 ++++++ Configuration/Services.yaml | 3 ++ .../Templates/Backend/FormcycleSelection.html | 49 ++++++++++++------- 7 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 Classes/Controller/BackendController.php create mode 100644 Configuration/Backend/AjaxRoutes.php create mode 100644 Configuration/JavaScriptModules.php diff --git a/Classes/Controller/BackendController.php b/Classes/Controller/BackendController.php new file mode 100644 index 0000000..25b3daf --- /dev/null +++ b/Classes/Controller/BackendController.php @@ -0,0 +1,45 @@ +formcycleService->resetAvailableFormsCache(); + + return $this->getAvailableForms($request); + } + + public function getAvailableForms(ServerRequestInterface $request): ResponseInterface + { + $forms = $this->formcycleService->getAvailableForms(); + $forms = FormcycleSelection::groupForms($forms); + + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:xm_formcycle/Resources/Private/Templates/Backend/FormcycleSelection.html'); + $view->assign('forms', $forms); + $html = $view->render(); + + $response = $this->responseFactory->createResponse() + ->withHeader('Content-Type', 'application/json; charset=utf-8'); + $response->getBody()->write( + json_encode(['html' => $html], JSON_THROW_ON_ERROR) + ); + return $response; + } +} diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index 00c902b..b86335d 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -19,9 +19,13 @@ public function render() try { /** @var FormcycleService $fcService */ $fcService = GeneralUtility::makeInstance(FormcycleService::class); - $forms = $fcService->getAvailableForms(); - $forms = self::groupForms($forms); - $view->assign('forms', $forms); + if ($fcService->hasAvailableFormsCached()) { + $forms = $fcService->getAvailableForms(); + $forms = self::groupForms($forms); + $view->assign('forms', $forms); + } else { + $view->assign('loading', true); + } } catch (FormcycleConfigurationException $e) { $errorCode = $e->getCode(); $view->assign('errorCode', $errorCode); @@ -33,7 +37,14 @@ public function render() } $resultArray = $this->initializeResultArray(); - $resultArray['html'] = $view->render(); + $resultArray['html'] = '
' . $view->render() . '
'; + + $parameterArray = $this->data['parameterArray']; + + //$resultArray['stylesheetFiles'] = $styleSheetPaths; + $resultArray['javaScriptModules'][] = \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction::create('@xima/xm-formcycle/FormcycleSelectionElement.js') + ->instance('hffe'); + return $resultArray; } diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 56b4bec..a6e0139 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -38,7 +38,6 @@ public function getAvailableForms(): array $forms = $this->cache->get('availableForms'); if ($forms === false) { $forms = $this->loadAvailableForms(); - $this->cache->set('availableForms', $forms); } return $forms; @@ -58,15 +57,23 @@ private function loadAvailableForms(): array try { $forms = json_decode($jsonResponse, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { - throw new FormcycleConnectionException('Loading available forms: Invalid JSON response of endpoint', 1709102526); + throw new FormcycleConnectionException( + 'Loading available forms: Invalid JSON response of endpoint', + 1709102526 + ); } if (!is_array($forms)) { - throw new FormcycleConnectionException('Loading available forms: Invalid JSON response of endpoint', 1709102526); + throw new FormcycleConnectionException( + 'Loading available forms: Invalid JSON response of endpoint', + 1709102526 + ); } self::encodePreviewImages($forms); + $this->cache->set('availableForms', $forms); + return $forms; } @@ -82,4 +89,14 @@ private static function encodePreviewImages(array &$forms): void } } } + + public function hasAvailableFormsCached(): bool + { + return $this->cache->has('availableForms'); + } + + public function resetAvailableFormsCache(): void + { + $this->cache->remove('availableForms'); + } } diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php new file mode 100644 index 0000000..41bfb5a --- /dev/null +++ b/Configuration/Backend/AjaxRoutes.php @@ -0,0 +1,14 @@ + [ + 'path' => '/xm-formcycle/form-selection', + 'target' => BackendController::class . '::getAvailableForms', + ], + 'xm_formcycle_form_reload' => [ + 'path' => '/xm-formcycle/form-reload', + 'target' => BackendController::class . '::reloadAvailableForms', + ], +]; diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php new file mode 100644 index 0000000..9648f17 --- /dev/null +++ b/Configuration/JavaScriptModules.php @@ -0,0 +1,14 @@ + [ + 'core', + 'backend', + ], + 'tags' => [ + 'backend.form', + ], + 'imports' => [ + '@xima/xm-formcycle/' => 'EXT:xm_formcycle/Resources/Public/JavaScript/Backend/', + ], +]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 5baf8c5..697cf40 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -16,3 +16,6 @@ services: public: true arguments: $cache: '@cache.xm_formcycle' + + Xima\XmFormcycle\Controller\BackendController: + tags: [ 'backend.controller' ] diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index 987f37e..8897cc9 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -4,23 +4,34 @@ - -
-

- {groupName} -

-
- + +
+
- +
+ + +
+ +
+

+ {groupName} + +

+
+ +
+
+
+
From 7666cf5baae17b581cf0e9f4cf7d033236ac8217 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 10:27:59 +0100 Subject: [PATCH 16/66] feat: add reload button --- .gitignore | 8 ++- Classes/Form/Element/FormcycleSelection.php | 2 +- .../Templates/Backend/FormcycleSelection.html | 21 ++++++-- .../Public/Css/Backend/FormcycleSelection.css | 3 ++ .../Backend/FormcycleSelectionElement.js | 51 +++++++++++++++++++ 5 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 Resources/Public/Css/Backend/FormcycleSelection.css create mode 100644 Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js diff --git a/.gitignore b/.gitignore index 6c0c6bf..903abc3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ .idea -public +public/.htaccess +public/_assets +public/fileadmin +public/typo3conf +public/index.php +public/typo3 +public/typo3temp config var vendor/ diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index b86335d..38321dd 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -38,10 +38,10 @@ public function render() $resultArray = $this->initializeResultArray(); $resultArray['html'] = '
' . $view->render() . '
'; + $resultArray['stylesheetFiles'][] = 'EXT:xm_formcycle/Resources/Public/Css/Backend/FormcycleSelection.css'; $parameterArray = $this->data['parameterArray']; - //$resultArray['stylesheetFiles'] = $styleSheetPaths; $resultArray['javaScriptModules'][] = \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction::create('@xima/xm-formcycle/FormcycleSelectionElement.js') ->instance('hffe'); diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index 8897cc9..dfc34dc 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -4,12 +4,23 @@ - -
- + + +
+ +
+
@@ -21,7 +32,7 @@


+ + diff --git a/Resources/Public/Css/Backend/FormcycleSelection.css b/Resources/Public/Css/Backend/FormcycleSelection.css new file mode 100644 index 0000000..9b56faf --- /dev/null +++ b/Resources/Public/Css/Backend/FormcycleSelection.css @@ -0,0 +1,3 @@ +#xm-available-forms-wrapper .card:hover { + +} diff --git a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js new file mode 100644 index 0000000..a68b12f --- /dev/null +++ b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js @@ -0,0 +1,51 @@ +import AjaxRequest from "@typo3/core/ajax/ajax-request.js"; + +export default class FormcycleSelectionElement { + + constructor(formId = '') { + this.init() + } + + init() { + const formsWrapper = document.querySelector('#xm-available-forms-wrapper') + if (!formsWrapper) { + this.loadForms(TYPO3.settings.ajaxUrls.xm_formcycle_form_selection) + } else { + this.initFormEvents() + } + } + + loadForms(url) { + const wrapper = document.querySelector('#xm-formcycle-forms') + + new AjaxRequest(url) + .get() + .then(async function (response) { + const resolved = await response.resolve(); + wrapper.innerHTML = resolved.html + }).then(() => { + this.initFormEvents() + }) + } + + initFormEvents() { + document.querySelectorAll('#xm-available-forms-wrapper .card').forEach(card => { + card.addEventListener('click', (e) => { + e.preventDefault() + console.log(e.currentTarget) + }) + }) + + const loadingSpinner = document.querySelector('#xm-loading-spinner') + const reloadButton = document.querySelector('#xm-reload-available-forms') + const formsWrapper = document.querySelector('#xm-available-forms-wrapper') + if (reloadButton) { + reloadButton.addEventListener('click', e => { + e.preventDefault() + formsWrapper.classList.add('hidden') + loadingSpinner.classList.remove('hidden') + this.loadForms(TYPO3.settings.ajaxUrls.xm_formcycle_form_reload) + }) + } + } +} From 40b6a50d14e3840e6dd56022be298fa4d439f3a2 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 10:52:16 +0100 Subject: [PATCH 17/66] feat: persist selected form id --- Classes/Form/Element/FormcycleSelection.php | 26 ++++++++++++++----- Configuration/TCA/Overrides/tt_content.php | 3 ++- .../Templates/Backend/FormcycleSelection.html | 2 +- .../Backend/FormcycleSelectionElement.js | 13 +++++++--- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index 38321dd..c7d3aeb 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -3,6 +3,7 @@ namespace Xima\XmFormcycle\Form\Element; use TYPO3\CMS\Backend\Form\Element\AbstractFormElement; +use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Fluid\View\StandaloneView; use Xima\XmFormcycle\Error\FormcycleConfigurationException; @@ -13,8 +14,16 @@ class FormcycleSelection extends AbstractFormElement { public function render() { + $fieldInformationResult = $this->renderFieldInformation(); + $resultArray = $this->mergeChildReturnIntoExistingResult( + $this->initializeResultArray(), + $fieldInformationResult, + false + ); + $view = GeneralUtility::makeInstance(StandaloneView::class); $view->setTemplatePathAndFilename('EXT:xm_formcycle/Resources/Private/Templates/Backend/FormcycleSelection.html'); + $view->assign('itemFormElValue', $this->data['parameterArray']['itemFormElValue']); try { /** @var FormcycleService $fcService */ @@ -36,14 +45,19 @@ public function render() $view->assign('errorType', 'connection'); } - $resultArray = $this->initializeResultArray(); - $resultArray['html'] = '
' . $view->render() . '
'; - $resultArray['stylesheetFiles'][] = 'EXT:xm_formcycle/Resources/Public/Css/Backend/FormcycleSelection.css'; + $hiddenInput = sprintf( + '', + htmlspecialchars($this->data['parameterArray']['itemFormElName']), + $this->data['parameterArray']['itemFormElID'], + htmlspecialchars($this->data['parameterArray']['itemFormElValue'], ENT_QUOTES), + htmlspecialchars($this->data['parameterArray']['itemFormElName']), + ); - $parameterArray = $this->data['parameterArray']; + $resultArray['html'] = '
' . $hiddenInput . '
' . $view->render() . '
'; + $resultArray['stylesheetFiles'][] = 'EXT:xm_formcycle/Resources/Public/Css/Backend/FormcycleSelection.css'; - $resultArray['javaScriptModules'][] = \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction::create('@xima/xm-formcycle/FormcycleSelectionElement.js') - ->instance('hffe'); + $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@xima/xm-formcycle/FormcycleSelectionElement.js') + ->instance($this->data['parameterArray']['itemFormElID']); return $resultArray; } diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php index c4ecc22..c6b7a14 100644 --- a/Configuration/TCA/Overrides/tt_content.php +++ b/Configuration/TCA/Overrides/tt_content.php @@ -24,7 +24,8 @@ 'tx_xmformcycle_form_id' => [ 'label' => 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:tx_xmformcycle_form_id.label', 'config' => [ - 'type' => 'formcycle-selection', + 'type' => 'user', + 'renderType' => 'formcycle-selection', ], ], ]; diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index dfc34dc..71784d9 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -32,7 +32,7 @@


- +
Preview {form.title}
diff --git a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js index a68b12f..74725de 100644 --- a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js +++ b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js @@ -2,7 +2,10 @@ import AjaxRequest from "@typo3/core/ajax/ajax-request.js"; export default class FormcycleSelectionElement { - constructor(formId = '') { + hiddenInputElement = null + + constructor(itemFormElID = '') { + this.hiddenInputElement = document.querySelector('#' + itemFormElID) this.init() } @@ -24,7 +27,7 @@ export default class FormcycleSelectionElement { const resolved = await response.resolve(); wrapper.innerHTML = resolved.html }).then(() => { - this.initFormEvents() + this.initFormEvents() }) } @@ -32,7 +35,11 @@ export default class FormcycleSelectionElement { document.querySelectorAll('#xm-available-forms-wrapper .card').forEach(card => { card.addEventListener('click', (e) => { e.preventDefault() - console.log(e.currentTarget) + document.querySelectorAll('#xm-available-forms-wrapper .card').forEach(card => { + card.classList.remove('active') + }) + e.currentTarget.classList.add('active') + this.hiddenInputElement.value = e.currentTarget.getAttribute('data-form-id') }) }) From 49bb735c0b17cdf7980e0646cbc71dce1126ce9e Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 11:32:10 +0100 Subject: [PATCH 18/66] feat: configure typoscript, fluid template --- Configuration/Icons.php | 10 ++++++ .../TCA/Overrides/tt_content_formcycle.php | 6 ++-- Configuration/TypoScript/constants.typoscript | 30 +++++----------- Configuration/TypoScript/setup.typoscript | 7 ++++ Configuration/page.tsconfig | 16 +++++++++ Resources/Private/Language/locallang.xlf | 5 ++- Resources/Private/Templates/Formcycle.html | 15 ++++++++ Resources/Public/Icons/ContentElement.svg | 1 + Resources/Public/Icons/Extension.svg | 1 + .../Public/Icons/xmformcycle_default.png | Bin 1280 -> 0 bytes composer.json | 5 +-- ext_localconf.php | 32 ------------------ 12 files changed, 69 insertions(+), 59 deletions(-) create mode 100644 Configuration/Icons.php create mode 100644 Configuration/page.tsconfig create mode 100644 Resources/Private/Templates/Formcycle.html create mode 100644 Resources/Public/Icons/ContentElement.svg create mode 100644 Resources/Public/Icons/Extension.svg delete mode 100644 Resources/Public/Icons/xmformcycle_default.png diff --git a/Configuration/Icons.php b/Configuration/Icons.php new file mode 100644 index 0000000..d05c8c8 --- /dev/null +++ b/Configuration/Icons.php @@ -0,0 +1,10 @@ + [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:xm_formcycle/Resources/Public/Icons/ContentElement.svg', + ], +]; diff --git a/Configuration/TCA/Overrides/tt_content_formcycle.php b/Configuration/TCA/Overrides/tt_content_formcycle.php index 2f7096a..4c68c01 100644 --- a/Configuration/TCA/Overrides/tt_content_formcycle.php +++ b/Configuration/TCA/Overrides/tt_content_formcycle.php @@ -6,14 +6,16 @@ 'tt_content', 'CType', [ - 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:wizard.name', + 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:wizard.title', 'formcycle', - 'content-listgroup', + 'xm-formcycle', ], 'felogin_login', 'after' ); +$GLOBALS['TCA']['tt_content']['ctrl']['typeicon_classes']['formcycle'] = 'xm-formcycle'; + $GLOBALS['TCA']['tt_content']['palettes']['formcycle'] = [ 'label' => 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:palettes.formcycle.title', 'showitem' => 'tx_xmformcycle_form_id', diff --git a/Configuration/TypoScript/constants.typoscript b/Configuration/TypoScript/constants.typoscript index 74e260a..1ee46c7 100644 --- a/Configuration/TypoScript/constants.typoscript +++ b/Configuration/TypoScript/constants.typoscript @@ -1,25 +1,11 @@ plugin.tx_xmformcycle { - view { - # VERSION 7.x.x - templateRootPath = EXT:xm_formcycle/Resources/Private/Templates/ - partialRootPath = EXT:xm_formcycle/Resources/Private/Partials/ - layoutRootPath = EXT:xm_formcycle/Resources/Private/Layouts/ + view { + templateRootPath = + partialRootPath = + layoutRootPath = + } - # Version 8.x.x - # cat=plugin.tx_xmformcycle/file; type=string; label=Path to template root (FE) - templateRootPaths.10 = EXT:xm_formcycle/Resources/Private/Templates/ - # cat=plugin.tx_xmformcycle/file; type=string; label=Path to template partials (FE) - partialRootPaths.10 = EXT:xm_formcycle/Resources/Private/Partials/ - # cat=plugin.tx_xmformcycle/file; type=string; label=Path to template layouts (FE) - layoutRootPaths.10 = EXT:xm_formcycle/Resources/Private/Layouts/ - } - persistence { - # cat=plugin.tx_xmformcycle//a; type=string; label=Default storage PID - storagePid = - } - settings { - # customsubcategory=assets=Assets - # cat=plugin.tx_xmformcycle/assets/10; type=boolean; label=Include JavaScript (disable it for your own asset management) - enableJs = 1 - } + settings { + enableJs = 1 + } } diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index 5ccdced..38ad849 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -71,5 +71,12 @@ tx_xmformcycle_ajax { tt_content.formcycle =< lib.contentElement tt_content.formcycle { + templateRootPaths.0 = EXT:xm_formcycle/Resources/Private/Templates/ + templateRootPaths.1 = {$plugin.tx_xmformcycle.view.templateRootPath} + partialRootPaths.0 = EXT:xm_formcycle/Resources/Private/Partials/ + partialRootPaths.1 = {$plugin.tx_xmformcycle.view.partialRootPath} + layoutRootPaths.0 = EXT:xm_formcycle/Resources/Private/Layouts/ + layoutRootPaths.1 = {$plugin.tx_xmformcycle.view.layoutRootPath} + templateName = Formcycle } diff --git a/Configuration/page.tsconfig b/Configuration/page.tsconfig new file mode 100644 index 0000000..9d63179 --- /dev/null +++ b/Configuration/page.tsconfig @@ -0,0 +1,16 @@ +mod { + wizards.newContentElement.wizardItems.forms { + elements { + formcycle { + iconIdentifier = xm-formcycle + title = LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:wizard.title + description = LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:wizard.description + tt_content_defValues { + CType = formcycle + } + } + } + + show := addToList(formcycle) + } +} diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 703d7bb..c4f7eea 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -9,9 +9,12 @@ Formcycle - + Formcycle + + Include a XIMA® FormCycle form + Form diff --git a/Resources/Private/Templates/Formcycle.html b/Resources/Private/Templates/Formcycle.html new file mode 100644 index 0000000..46f59e1 --- /dev/null +++ b/Resources/Private/Templates/Formcycle.html @@ -0,0 +1,15 @@ + + + + +
+ + FORM! + + {data.tx_xmformcycle_form_id} + + +
+ +
+ diff --git a/Resources/Public/Icons/ContentElement.svg b/Resources/Public/Icons/ContentElement.svg new file mode 100644 index 0000000..d135f12 --- /dev/null +++ b/Resources/Public/Icons/ContentElement.svg @@ -0,0 +1 @@ + diff --git a/Resources/Public/Icons/Extension.svg b/Resources/Public/Icons/Extension.svg new file mode 100644 index 0000000..49f9f00 --- /dev/null +++ b/Resources/Public/Icons/Extension.svg @@ -0,0 +1 @@ + diff --git a/Resources/Public/Icons/xmformcycle_default.png b/Resources/Public/Icons/xmformcycle_default.png deleted file mode 100644 index 97e22106acbd3e0e9ef69f002f35d8acd65eacae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1280 zcmeAS@N?(olHy`uVBq!ia0vp^{2!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@pr;JNj1^1m z%NQ6KPi2NgltlRYSS9D@>LsS+C#C9D0BS3v*iS0onb8|oS8W7AZUnTBv4iY|~0L>t&w z5X)?UL1X1yl$uzQUlfv`pJP{$n3-3imzP?i0QA3}sf|9m0)#_tKqgxG7iFdby$bS> zouQ3Bh8R@6jXub?NWO;zEm#y7wsu@Vn_%%|$F)K>K#YNbX@RGUV~B=mZcwy$NTA5S zYj)4iNa|nKSd@0d^#T*Su8WY-Ar2CxkE2eYoGT?XoD$ms;xAf3*4J};* zojp^gw<`&7ALU-W@SMf-ZQ-vik_2k`tLN`MANBh8x9wsdE%+J=__&t6cR#+S?al0Y z^TMh#iprO(-D7{o(;*=>LBdjrA$VWRS%G>d&K#b_Tvu(9b%Umcdl&wz{ZsNhXIj!! znH>f`(MGH)C#@PpPdcaRn&t-y2mN6+{~&yRrNq-2hf6zmE#7H-^w!ERwfTx4?r<)e z5VAvZ-t&}4hivAp*3hnZwLc$uCU{1CMfmNOPu$GQZ|&as&gJ>-xeHQ)TK=~_5{!s; z?`_}1BG0;_u1ig(PaZ=UGWrhmV&)9uG1_s%u_2X|cb{&~;U^;f{>e+Pa{ zy>>%7Rr+k~#$QbS+SfO{vGM;s&pSf*+>C|nHXP2UGN)_5bDf@k?$p$+n^xKcxBTc) zZuhlyij!a}Gt-@z=W6jT=2U0Z{;M2MOwXM8v?M~UK0Kx z_vl|L$WzjBf1+c(;L@QD#y0jJ7k)n}_R+q(b6P<6fdiqUkENC{T<_i&@h4}?^=+N{ z*(oYQvJ+pvY>B@+Yttj|bJHF#UR=!-JWqflyLZY9HropT=RCz>cbPcW){^c?%aF2*Mal3 zyu9gg-y+kND*zF>jxEY}xw7e;b-lmrCDzyz<6c qg}qTrr~J}eX&FD^&b{KJ{}^_ilASVj%ii^%a>mou&t;ucLK6T \Xima\XmFormcycle\Form\Element\FcElement::class, ]; -/** - * Icon Registry - */ -$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class); -$iconRegistry->registerIcon( - 'xmformcycle-icon', - \TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class, - ['source' => 'EXT:xm_formcycle/Resources/Public/Icons/xmformcycle_default.png'] -); - -/** - * Wizards - */ -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig( - 'mod { - wizards.newContentElement.wizardItems.plugins { - elements { - search { - iconIdentifier = xmformcycle-icon - title = LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:tx_xmformcycle_domain_model_formcycle.wizard.name - description = LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:tx_xmformcycle_domain_model_formcycle.wizard.description - tt_content_defValues { - CType = list - list_type = xmformcycle_xmformcycle - } - } - } - show = * - } - }' -); - // register custom form element $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1709039883] = [ 'nodeName' => 'formcycle-selection', From ae604062726e82553c8666d9c112c5b79478b104 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 13:08:41 +0100 Subject: [PATCH 19/66] feat: add backend preview --- Classes/Preview/FormcyclePreviewRenderer.php | 53 +++++++++++++++++++ Classes/Service/FormcycleService.php | 9 ++++ Configuration/Services.yaml | 3 ++ .../TCA/Overrides/tt_content_formcycle.php | 2 + .../Templates/Backend/FormcycleSelection.html | 2 +- 5 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 Classes/Preview/FormcyclePreviewRenderer.php diff --git a/Classes/Preview/FormcyclePreviewRenderer.php b/Classes/Preview/FormcyclePreviewRenderer.php new file mode 100644 index 0000000..eeaded4 --- /dev/null +++ b/Classes/Preview/FormcyclePreviewRenderer.php @@ -0,0 +1,53 @@ +getRecord(); + + if (!isset($row['tx_xmformcycle_form_id'])) { + $content = 'Database field not found, please update database schema'; + return $this->linkEditContent($content, $row); + } + + if (!$row['tx_xmformcycle_form_id'] || !is_string($row['tx_xmformcycle_form_id'])) { + $content = 'No form selected'; + return $this->linkEditContent($content, $row); + } + + try { + /** @var FormcycleService $formcycleService */ + $formcycleService = GeneralUtility::makeInstance(FormcycleService::class); + } catch (FormcycleConfigurationException $e) { + $content = 'Formcycle extension configuration error'; + return $this->linkEditContent($content, $row); + } + + $cacheExists = $formcycleService->hasAvailableFormsCached(); + $formConfiguration = $cacheExists ? $formcycleService->getAvailableFormConfigurationByFormId($row['tx_xmformcycle_form_id']) : []; + if (empty($formConfiguration)) { + $content = 'Configured form ID: ' . $row['tx_xmformcycle_form_id']; + return $this->linkEditContent($content, $row); + } + + if ($formConfiguration['thumbnail'] ?? false) { + $content .= 'Formcycle form preview
'; + } + + if ($formConfiguration['title'] ?? false) { + $content .= $formConfiguration['title']; + } + + return $this->linkEditContent($content, $row); + } +} diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index a6e0139..ee1d687 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -99,4 +99,13 @@ public function resetAvailableFormsCache(): void { $this->cache->remove('availableForms'); } + + public function getAvailableFormConfigurationByFormId(string $formId): array + { + $forms = $this->getAvailableForms(); + + $index = array_search((int)$formId, array_column($forms, 'form_id'), true); + + return $index !== false ? $forms[$index] : []; + } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 697cf40..aef1445 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -19,3 +19,6 @@ services: Xima\XmFormcycle\Controller\BackendController: tags: [ 'backend.controller' ] + + Xima\XmFormcycle\Preview\FormcyclePreviewRenderer: + public: true diff --git a/Configuration/TCA/Overrides/tt_content_formcycle.php b/Configuration/TCA/Overrides/tt_content_formcycle.php index 4c68c01..f8b91b6 100644 --- a/Configuration/TCA/Overrides/tt_content_formcycle.php +++ b/Configuration/TCA/Overrides/tt_content_formcycle.php @@ -1,6 +1,7 @@ FormcyclePreviewRenderer::class, ]; diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index 71784d9..8855f48 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -32,7 +32,7 @@


- +
Preview {form.title}
From 740fc7bad2a6d3610ee1d9e2c679ea973d56c686 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 14:04:19 +0100 Subject: [PATCH 20/66] feat: add flexform, settings toggle --- Classes/Form/Element/FormcycleSelection.php | 2 +- Configuration/FlexForms/flexform_list.xml | 65 ------------------- Configuration/TCA/Overrides/tt_content.php | 23 ++----- .../TCA/Overrides/tt_content_formcycle.php | 2 +- .../Templates/Backend/FormcycleSelection.html | 8 ++- .../Public/Css/Backend/FormcycleSelection.css | 14 +++- .../Backend/FormcycleSelectionElement.js | 8 +++ 7 files changed, 35 insertions(+), 87 deletions(-) diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index c7d3aeb..0b2a94f 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -53,7 +53,7 @@ public function render() htmlspecialchars($this->data['parameterArray']['itemFormElName']), ); - $resultArray['html'] = '
' . $hiddenInput . '
' . $view->render() . '
'; + $resultArray['html'] = '
' . $hiddenInput . '
' . $view->render() . '
'; $resultArray['stylesheetFiles'][] = 'EXT:xm_formcycle/Resources/Public/Css/Backend/FormcycleSelection.css'; $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@xima/xm-formcycle/FormcycleSelectionElement.js') diff --git a/Configuration/FlexForms/flexform_list.xml b/Configuration/FlexForms/flexform_list.xml index 9c2e75b..28e8f83 100644 --- a/Configuration/FlexForms/flexform_list.xml +++ b/Configuration/FlexForms/flexform_list.xml @@ -52,24 +52,6 @@ - - - - - user - startNewElement - - - - - - - - user - startOpenElement - - - @@ -108,15 +90,6 @@ - - - - - user - startOpenElement - - - @@ -166,15 +139,6 @@ - - - - - user - startOpenElement - - - @@ -197,34 +161,5 @@ - - - - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab4_name - - array - - - - - - user - 212x108 - fcElement - - - - - - - - user - startOpenElement - - - - - - diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php index c6b7a14..ddee64e 100644 --- a/Configuration/TCA/Overrides/tt_content.php +++ b/Configuration/TCA/Overrides/tt_content.php @@ -1,25 +1,9 @@ [ 'label' => 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:tx_xmformcycle_form_id.label', @@ -29,5 +13,10 @@ ], ], ]; +ExtensionManagementUtility::addTCAcolumns('tt_content', $tempFields); -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('tt_content', $tempFields); +ExtensionManagementUtility::addPiFlexFormValue( + '*', + 'FILE:EXT:xm_formcycle/Configuration/FlexForms/flexform_list.xml', + 'formcycle' +); diff --git a/Configuration/TCA/Overrides/tt_content_formcycle.php b/Configuration/TCA/Overrides/tt_content_formcycle.php index f8b91b6..675ffb6 100644 --- a/Configuration/TCA/Overrides/tt_content_formcycle.php +++ b/Configuration/TCA/Overrides/tt_content_formcycle.php @@ -19,7 +19,7 @@ $GLOBALS['TCA']['tt_content']['palettes']['formcycle'] = [ 'label' => 'LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:palettes.formcycle.title', - 'showitem' => 'tx_xmformcycle_form_id', + 'showitem' => 'tx_xmformcycle_form_id,--linebreak--,pi_flexform', ]; $GLOBALS['TCA']['tt_content']['types']['formcycle'] = [ diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index 8855f48..3a41d9d 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -7,11 +7,15 @@
diff --git a/Resources/Public/Css/Backend/FormcycleSelection.css b/Resources/Public/Css/Backend/FormcycleSelection.css index 9b56faf..0eae9ad 100644 --- a/Resources/Public/Css/Backend/FormcycleSelection.css +++ b/Resources/Public/Css/Backend/FormcycleSelection.css @@ -1,3 +1,15 @@ -#xm-available-forms-wrapper .card:hover { +div:has(> #xm-formcycle-forms:not(.open-settings)) ~ .form-group { + display: none; +} + +#xm-formcycle-forms.open-settings #xm-available-forms-wrapper { + display: none; +} + +#xm-formcycle-forms.open-settings > .btn-group { + margin-bottom: 0 !important; +} +div:has(> #xm-formcycle-forms.open-settings) ~ .form-group legend.form-legend { + display: none; } diff --git a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js index 74725de..33a3821 100644 --- a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js +++ b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js @@ -16,6 +16,14 @@ export default class FormcycleSelectionElement { } else { this.initFormEvents() } + + const settingsButton = document.querySelector('#xm-settings') + const wrapper = document.querySelector('#xm-formcycle-forms') + settingsButton.addEventListener('click', e => { + e.preventDefault() + e.currentTarget.classList.toggle('active') + wrapper.classList.toggle('open-settings') + }) } loadForms(url) { From eda97b15755ce9dc6e05c0a0fecf45974dc5fe71 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 14:40:00 +0100 Subject: [PATCH 21/66] feat: translations --- Resources/Private/Language/locallang.xlf | 9 ++++++++ .../Templates/Backend/FormcycleSelection.html | 6 ++--- .../Backend/FormcycleSelectionElement.js | 23 ++++++++++++------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index c4f7eea..e2b464e 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -48,6 +48,15 @@ Ungrouped + + Reload + + + Settings + + + Formcycle Admin + XIMA® FormCycle forms in TYPO3 Frontend diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index 3a41d9d..1dff7d8 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -8,15 +8,15 @@
- Open Formcycle Admin + {f:translate(key: 'button.admin', extensionName:'XmFormcycle')}
diff --git a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js index 33a3821..39833ef 100644 --- a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js +++ b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js @@ -16,14 +16,6 @@ export default class FormcycleSelectionElement { } else { this.initFormEvents() } - - const settingsButton = document.querySelector('#xm-settings') - const wrapper = document.querySelector('#xm-formcycle-forms') - settingsButton.addEventListener('click', e => { - e.preventDefault() - e.currentTarget.classList.toggle('active') - wrapper.classList.toggle('open-settings') - }) } loadForms(url) { @@ -51,16 +43,31 @@ export default class FormcycleSelectionElement { }) }) + const settingsButton = document.querySelector('#xm-settings') + const wrapper = document.querySelector('#xm-formcycle-forms') + settingsButton.addEventListener('click', e => { + e.preventDefault() + e.currentTarget.classList.toggle('active') + wrapper.classList.toggle('open-settings') + }) + const loadingSpinner = document.querySelector('#xm-loading-spinner') const reloadButton = document.querySelector('#xm-reload-available-forms') const formsWrapper = document.querySelector('#xm-available-forms-wrapper') if (reloadButton) { reloadButton.addEventListener('click', e => { e.preventDefault() + // hide form gallery, display loading spinner formsWrapper.classList.add('hidden') loadingSpinner.classList.remove('hidden') this.loadForms(TYPO3.settings.ajaxUrls.xm_formcycle_form_reload) + + // close settings if open + settingsButton.classList.remove('active') + wrapper.classList.remove('open-settings') }) } + + } } From 49097710a3153c17af0fd31c751464e804c93ba3 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 14:44:12 +0100 Subject: [PATCH 22/66] feat: add admin url to button --- Classes/Dto/FormcycleConfiguration.php | 5 +++ Classes/Form/Element/FormcycleSelection.php | 1 + Classes/Service/FormcycleService.php | 37 +++++++++++-------- .../Templates/Backend/FormcycleSelection.html | 2 +- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php index 4eb1ecc..4b51125 100644 --- a/Classes/Dto/FormcycleConfiguration.php +++ b/Classes/Dto/FormcycleConfiguration.php @@ -77,4 +77,9 @@ public function getFormListUrl(): string $this->client, ); } + + public function getAdminUrl(): string + { + return $this->formCycleFrontendUrl ?: $this->formCycleUrl; + } } diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index 0b2a94f..7464205 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -28,6 +28,7 @@ public function render() try { /** @var FormcycleService $fcService */ $fcService = GeneralUtility::makeInstance(FormcycleService::class); + $view->assign('adminUrl', $fcService->getAdminUrl()); if ($fcService->hasAvailableFormsCached()) { $forms = $fcService->getAvailableForms(); $forms = self::groupForms($forms); diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index ee1d687..9f3697a 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -30,6 +30,25 @@ public function __construct( $this->configuration = FormcycleConfiguration::createFromExtensionConfiguration($extConfig); } + public function hasAvailableFormsCached(): bool + { + return $this->cache->has('availableForms'); + } + + public function resetAvailableFormsCache(): void + { + $this->cache->remove('availableForms'); + } + + public function getAvailableFormConfigurationByFormId(string $formId): array + { + $forms = $this->getAvailableForms(); + + $index = array_search((int)$formId, array_column($forms, 'form_id'), true); + + return $index !== false ? $forms[$index] : []; + } + /** * @throws FormcycleConnectionException */ @@ -90,22 +109,8 @@ private static function encodePreviewImages(array &$forms): void } } - public function hasAvailableFormsCached(): bool + public function getAdminUrl(): string { - return $this->cache->has('availableForms'); - } - - public function resetAvailableFormsCache(): void - { - $this->cache->remove('availableForms'); - } - - public function getAvailableFormConfigurationByFormId(string $formId): array - { - $forms = $this->getAvailableForms(); - - $index = array_search((int)$formId, array_column($forms, 'form_id'), true); - - return $index !== false ? $forms[$index] : []; + return $this->configuration->getAdminUrl(); } } diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index 1dff7d8..d69f203 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -14,7 +14,7 @@ {f:translate(key: 'button.settings', extensionName:'XmFormcycle')} - + {f:translate(key: 'button.admin', extensionName:'XmFormcycle')} From 9215871b8bf3ee53b420c15e1d2c620f334cb8f8 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 16:19:02 +0100 Subject: [PATCH 23/66] feat: migrate integration mode constants to enums --- Classes/Dto/FormcycleConfiguration.php | 24 ++++++------------- Classes/Dto/IntegrationMode.php | 10 ++++++++ Classes/Service/FormcycleService.php | 6 +++++ Tests/Unit/Dto/FormcycleConfigurationTest.php | 9 ++++--- 4 files changed, 27 insertions(+), 22 deletions(-) create mode 100644 Classes/Dto/IntegrationMode.php diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php index 4b51125..998fd60 100644 --- a/Classes/Dto/FormcycleConfiguration.php +++ b/Classes/Dto/FormcycleConfiguration.php @@ -7,10 +7,6 @@ final class FormcycleConfiguration { - public const INTEGRATION_MODE_INTEGRATED = 'integrated'; - public const INTEGRATION_MODE_IFRAME = 'iFrame'; - public const INTEGRATION_MODE_AJAX = 'AJAX'; - private string $formCycleUrl; private string $formCycleFrontendUrl; @@ -19,7 +15,7 @@ final class FormcycleConfiguration private string $formCyclePass; - private string $integrationMode; + private IntegrationMode $integrationMode; private string $client = '24871'; @@ -43,18 +39,7 @@ public static function createFromExtensionConfiguration(array $extConfiguration) throw new FormcycleConfigurationException('No formCycleUser or formCyclePass set', 1709052037); } - $mode = $extConfiguration['integrationMode'] ?? ''; - $config->integrationMode = $mode ?: self::INTEGRATION_MODE_INTEGRATED; - if (!in_array( - $config->integrationMode, - [self::INTEGRATION_MODE_IFRAME, self::INTEGRATION_MODE_INTEGRATED, self::INTEGRATION_MODE_AJAX], - true - )) { - throw new FormcycleConfigurationException( - 'Invalid integration mode "' . $config->integrationMode . '"', - 1709052040 - ); - } + $config->integrationMode = IntegrationMode::tryFrom($extConfiguration['integrationMode'] ?? '') ?? IntegrationMode::Integrated; $config->formCycleFrontendUrl = $extConfiguration['formCycleFrontendUrl'] ?? ''; if ($config->formCycleFrontendUrl && !GeneralUtility::isValidUrl($config->formCycleFrontendUrl)) { @@ -82,4 +67,9 @@ public function getAdminUrl(): string { return $this->formCycleFrontendUrl ?: $this->formCycleUrl; } + + public function getIntegrationMode(): IntegrationMode + { + return $this->integrationMode; + } } diff --git a/Classes/Dto/IntegrationMode.php b/Classes/Dto/IntegrationMode.php new file mode 100644 index 0000000..c0f66cf --- /dev/null +++ b/Classes/Dto/IntegrationMode.php @@ -0,0 +1,10 @@ +configuration->getAdminUrl(); } + + public function getIntegrationMode(): IntegrationMode + { + return $this->configuration->getIntegrationMode(); + } } diff --git a/Tests/Unit/Dto/FormcycleConfigurationTest.php b/Tests/Unit/Dto/FormcycleConfigurationTest.php index 39722f2..f44593a 100644 --- a/Tests/Unit/Dto/FormcycleConfigurationTest.php +++ b/Tests/Unit/Dto/FormcycleConfigurationTest.php @@ -4,6 +4,7 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; use Xima\XmFormcycle\Dto\FormcycleConfiguration; +use Xima\XmFormcycle\Dto\IntegrationMode; use Xima\XmFormcycle\Error\FormcycleConfigurationException; class FormcycleConfigurationTest extends UnitTestCase @@ -54,11 +55,9 @@ public function testMissingPassword(): void FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } - public function testInvalidIntegrationMode(): void + public function testDefaultIntegrationMode(): void { - $this->expectException(FormcycleConfigurationException::class); - $this->expectExceptionCode(1709052040); - $this->validExtensionConfiguration['integrationMode'] = 'ajax'; - FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + $config = FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + self::assertEquals(IntegrationMode::Integrated, $config->getIntegrationMode()); } } From 5aa69d1c1c3773842ff24079755259cf4ad98b0b Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 16:59:17 +0100 Subject: [PATCH 24/66] feat: add DataProcessors, ElementSettigs DTO --- Classes/DataProcessing/AbstractProcessor.php | 30 +++++++ Classes/DataProcessing/AjaxProcessor.php | 24 ++++++ .../DataProcessing/IntegratedProcessor.php | 24 ++++++ Classes/DataProcessing/iFrameProcessor.php | 30 +++++++ Classes/Dto/ElementSettings.php | 48 +++++++++++ Classes/Dto/IntegrationMode.php | 2 +- Configuration/FlexForms/flexform_list.xml | 82 ++++++++----------- Configuration/Services.yaml | 15 ++++ Configuration/TypoScript/setup.typoscript | 81 ++++++++++-------- .../JavaScript/Frontend/FormcycleIframe.js | 1 + 10 files changed, 254 insertions(+), 83 deletions(-) create mode 100644 Classes/DataProcessing/AbstractProcessor.php create mode 100644 Classes/DataProcessing/AjaxProcessor.php create mode 100644 Classes/DataProcessing/IntegratedProcessor.php create mode 100644 Classes/DataProcessing/iFrameProcessor.php create mode 100644 Classes/Dto/ElementSettings.php create mode 100644 Resources/Public/JavaScript/Frontend/FormcycleIframe.js diff --git a/Classes/DataProcessing/AbstractProcessor.php b/Classes/DataProcessing/AbstractProcessor.php new file mode 100644 index 0000000..707997a --- /dev/null +++ b/Classes/DataProcessing/AbstractProcessor.php @@ -0,0 +1,30 @@ +formcycleService->getIntegrationMode(); + $this->settings = ElementSettings::createFromContentElement( + $this->flexFormService, + $cObj, + $defaultIntegrationMode + ); + } +} diff --git a/Classes/DataProcessing/AjaxProcessor.php b/Classes/DataProcessing/AjaxProcessor.php new file mode 100644 index 0000000..e19abbb --- /dev/null +++ b/Classes/DataProcessing/AjaxProcessor.php @@ -0,0 +1,24 @@ +initElementSettings($cObj); + + if ($this->settings->integrationMode !== IntegrationMode::Ajax) { + return $processedData; + } + + return $processedData; + } +} diff --git a/Classes/DataProcessing/IntegratedProcessor.php b/Classes/DataProcessing/IntegratedProcessor.php new file mode 100644 index 0000000..dce9c0b --- /dev/null +++ b/Classes/DataProcessing/IntegratedProcessor.php @@ -0,0 +1,24 @@ +initElementSettings($cObj); + + if ($this->settings->integrationMode !== IntegrationMode::Integrated) { + return $processedData; + } + + return $processedData; + } +} diff --git a/Classes/DataProcessing/iFrameProcessor.php b/Classes/DataProcessing/iFrameProcessor.php new file mode 100644 index 0000000..98ec28e --- /dev/null +++ b/Classes/DataProcessing/iFrameProcessor.php @@ -0,0 +1,30 @@ +initElementSettings($cObj); + + if ($this->settings->integrationMode !== IntegrationMode::Iframe) { + return $processedData; + } + + /** @var PageRenderer $pageRenderer */ + $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/FormcycleIframe.js'); + + return $processedData; + } +} diff --git a/Classes/Dto/ElementSettings.php b/Classes/Dto/ElementSettings.php new file mode 100644 index 0000000..65710c3 --- /dev/null +++ b/Classes/Dto/ElementSettings.php @@ -0,0 +1,48 @@ +convertFlexFormContentToArray($cObj->data['pi_flexform'] ?? ''); + + $settings = new self(); + $settings->formId = $cObj->data['tx_xmformcycle_form_id'] ?? ''; + + $settings->successResponsePid = $xml['settings']['xf']['siteok'] ?? 0; + $settings->failureResponsePid = $xml['settings']['xf']['siteerror'] ?? 0; + $settings->loadFormcycleJquery = (bool)($xml['settings']['xf']['useFcjQuery'] ?? 1); + $settings->loadFormcycleJqueryUi = (bool)($xml['settings']['xf']['useFcjQueryUi'] ?? 0); + $settings->loadResponseJs = (bool)($xml['settings']['xf']['useFcBootStrap'] ?? 0); + $settings->additionalParameters = $xml['settings']['xf']['useFcUrlParams'] ?? ''; + + $integrationMode = $xml['settings']['xf']['integrationMode'] ?? ''; + $settings->integrationMode = IntegrationMode::tryFrom($integrationMode) ?? $defaultIntegrationMode; + + return $settings; + } +} diff --git a/Classes/Dto/IntegrationMode.php b/Classes/Dto/IntegrationMode.php index c0f66cf..b99d162 100644 --- a/Classes/Dto/IntegrationMode.php +++ b/Classes/Dto/IntegrationMode.php @@ -6,5 +6,5 @@ enum IntegrationMode: string { case Integrated = 'integrated'; case Ajax = 'AJAX'; - case Iframe = 'iframe'; + case Iframe = 'iFrame'; } diff --git a/Configuration/FlexForms/flexform_list.xml b/Configuration/FlexForms/flexform_list.xml index 28e8f83..46970df 100644 --- a/Configuration/FlexForms/flexform_list.xml +++ b/Configuration/FlexForms/flexform_list.xml @@ -3,23 +3,51 @@ 1 - + - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab1_name + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab2_name array - + - + - input - 10 - trim + group + db + pages + 1 + 1 + none + 0 - + + + + + + + group + db + pages + 1 + 1 + 0 + + + + + + + + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab1_name + + array + @@ -55,44 +83,6 @@ - - - - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab2_name - - array - - - - - - group - db - pages - 1 - 1 - none - 0 - - - - - - - - - group - db - pages - 1 - 1 - 0 - - - - - - diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index aef1445..7ce8986 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -22,3 +22,18 @@ services: Xima\XmFormcycle\Preview\FormcyclePreviewRenderer: public: true + + Xima\XmFormcycle\DataProcessing\AjaxProcessor: + tags: + - name: 'data.processor' + identifier: 'formcycle-ajax' + + Xima\XmFormcycle\DataProcessing\iFrameProcessor: + tags: + - name: 'data.processor' + identifier: 'formcycle-iframe' + + Xima\XmFormcycle\DataProcessing\IntegratedProcessor: + tags: + - name: 'data.processor' + identifier: 'formcycle-integrated' diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index 38ad849..e7ea23a 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -1,22 +1,25 @@ plugin.tx_xmformcycle { - view { - templateRootPaths.10 = {$plugin.tx_xmformcycle.view.templateRootPath} - partialRootPaths.10 = {$plugin.tx_xmformcycle.view.partialRootPath} - layoutRootPaths.10 = {$plugin.tx_xmformcycle.view.layoutRootPath} - } - persistence { - storagePid = {$plugin.tx_xmformcycle.persistence.storagePid} - } - features { - # uncomment the following line to enable the new Property Mapper. - # rewrittenPropertyMapper = 1 - } - settings { - enableJs = {$plugin.tx_xmformcycle.settings.enableJs} - jsFiles { - 10 = EXT:Resources/Public/Js/xm_formcycle.js - } - } + view { + templateRootPaths.10 = {$plugin.tx_xmformcycle.view.templateRootPath} + partialRootPaths.10 = {$plugin.tx_xmformcycle.view.partialRootPath} + layoutRootPaths.10 = {$plugin.tx_xmformcycle.view.layoutRootPath} + } + + persistence { + storagePid = {$plugin.tx_xmformcycle.persistence.storagePid} + } + + features { + # uncomment the following line to enable the new Property Mapper. + # rewrittenPropertyMapper = 1 + } + + settings { + enableJs = {$plugin.tx_xmformcycle.settings.enableJs} + jsFiles { + 10 = EXT:Resources/Public/Js/xm_formcycle.js + } + } } plugin.tx_xmformcycle._CSS_DEFAULT_STYLE ( @@ -48,25 +51,25 @@ page.includeJSFooter.tx_xmformcycle = EXT:xm_formcycle/Resources/Public/Js/xm_fo tx_xmformcycle_ajax = PAGE tx_xmformcycle_ajax { - typeNum = 1464705954 - config { - disableAllHeaderCode = 1 - additionalHeaders = Content-type:text/html - xhtml_cleaning = 0 - admPanel = 0 - debug = 0 - no_cache = 1 - } + typeNum = 1464705954 + config { + disableAllHeaderCode = 1 + additionalHeaders = Content-type:text/html + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + no_cache = 1 + } - 10 = USER - 10 { - userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run - extensionName = XmFormcycle - pluginName = Xmformcycle - vendorName = Xima - controller = Formcycle - settings =< plugin.tx_xmformcycle.settings - } + 10 = USER + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = XmFormcycle + pluginName = Xmformcycle + vendorName = Xima + controller = Formcycle + settings =< plugin.tx_xmformcycle.settings + } } tt_content.formcycle =< lib.contentElement @@ -79,4 +82,10 @@ tt_content.formcycle { layoutRootPaths.1 = {$plugin.tx_xmformcycle.view.layoutRootPath} templateName = Formcycle + + dataProcessing { + 1709129505 = formcycle-ajax + 1709129509 = formcycle-iframe + 1709129512 = formcycle-integrated + } } diff --git a/Resources/Public/JavaScript/Frontend/FormcycleIframe.js b/Resources/Public/JavaScript/Frontend/FormcycleIframe.js new file mode 100644 index 0000000..371fdfb --- /dev/null +++ b/Resources/Public/JavaScript/Frontend/FormcycleIframe.js @@ -0,0 +1 @@ +console.log('hello') From c518db3533ca40e43fe1d599342e8b2fa7915958 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 18:16:13 +0100 Subject: [PATCH 25/66] feat: add iframe integration --- Classes/DataProcessing/AbstractProcessor.php | 4 +- Classes/DataProcessing/iFrameProcessor.php | 3 ++ Classes/Dto/ElementSettings.php | 23 +++++----- Classes/Dto/FormcycleConfiguration.php | 5 ++ Classes/Service/FormcycleService.php | 46 +++++++++++++++++-- .../Templates/Backend/FormcycleSelection.html | 2 +- Resources/Private/Templates/Formcycle.html | 4 ++ .../JavaScript/Frontend/FormcycleIframe.js | 6 ++- 8 files changed, 71 insertions(+), 22 deletions(-) diff --git a/Classes/DataProcessing/AbstractProcessor.php b/Classes/DataProcessing/AbstractProcessor.php index 707997a..6f5ab26 100644 --- a/Classes/DataProcessing/AbstractProcessor.php +++ b/Classes/DataProcessing/AbstractProcessor.php @@ -13,18 +13,16 @@ abstract class AbstractProcessor implements DataProcessorInterface protected ElementSettings $settings; public function __construct( - private readonly FormcycleService $formcycleService, + protected readonly FormcycleService $formcycleService, private readonly FlexFormService $flexFormService ) { } protected function initElementSettings(ContentObjectRenderer $cObj): void { - $defaultIntegrationMode = $this->formcycleService->getIntegrationMode(); $this->settings = ElementSettings::createFromContentElement( $this->flexFormService, $cObj, - $defaultIntegrationMode ); } } diff --git a/Classes/DataProcessing/iFrameProcessor.php b/Classes/DataProcessing/iFrameProcessor.php index 98ec28e..5407c2c 100644 --- a/Classes/DataProcessing/iFrameProcessor.php +++ b/Classes/DataProcessing/iFrameProcessor.php @@ -25,6 +25,9 @@ public function process( $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/FormcycleIframe.js'); + $processedData['iframe'] = []; + $processedData['iframe']['url'] = $this->formcycleService->getIframeUrl($this->settings); + return $processedData; } } diff --git a/Classes/Dto/ElementSettings.php b/Classes/Dto/ElementSettings.php index 65710c3..e861d2f 100644 --- a/Classes/Dto/ElementSettings.php +++ b/Classes/Dto/ElementSettings.php @@ -7,13 +7,13 @@ class ElementSettings { - public int $successResponsePid = 0; + public int $successPid = 0; - public int $failureResponsePid = 0; + public int $errorPid = 0; public string $formId = ''; - public IntegrationMode $integrationMode; + public ?IntegrationMode $integrationMode = null; public bool $loadFormcycleJquery = true; @@ -23,25 +23,24 @@ class ElementSettings public string $additionalParameters = ''; + public string $serverUri = ''; + public static function createFromContentElement( FlexFormService $flexFormService, ContentObjectRenderer $cObj, - IntegrationMode $defaultIntegrationMode ): self { $xml = $flexFormService->convertFlexFormContentToArray($cObj->data['pi_flexform'] ?? ''); $settings = new self(); $settings->formId = $cObj->data['tx_xmformcycle_form_id'] ?? ''; - $settings->successResponsePid = $xml['settings']['xf']['siteok'] ?? 0; - $settings->failureResponsePid = $xml['settings']['xf']['siteerror'] ?? 0; + $settings->successPid = $xml['settings']['xf']['siteok'] ?? 0; + $settings->errorPid = $xml['settings']['xf']['siteerror'] ?? 0; $settings->loadFormcycleJquery = (bool)($xml['settings']['xf']['useFcjQuery'] ?? 1); - $settings->loadFormcycleJqueryUi = (bool)($xml['settings']['xf']['useFcjQueryUi'] ?? 0); - $settings->loadResponseJs = (bool)($xml['settings']['xf']['useFcBootStrap'] ?? 0); - $settings->additionalParameters = $xml['settings']['xf']['useFcUrlParams'] ?? ''; - - $integrationMode = $xml['settings']['xf']['integrationMode'] ?? ''; - $settings->integrationMode = IntegrationMode::tryFrom($integrationMode) ?? $defaultIntegrationMode; + $settings->loadFormcycleJqueryUi = (bool)($xml['settings']['xf']['useFcjQueryUi'] ?? 0); + $settings->loadResponseJs = (bool)($xml['settings']['xf']['useFcBootStrap'] ?? 0); + $settings->additionalParameters = $xml['settings']['xf']['useFcUrlParams'] ?? ''; + $settings->integrationMode = IntegrationMode::tryFrom($xml['settings']['xf']['integrationMode']); return $settings; } diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php index 998fd60..323a92e 100644 --- a/Classes/Dto/FormcycleConfiguration.php +++ b/Classes/Dto/FormcycleConfiguration.php @@ -63,6 +63,11 @@ public function getFormListUrl(): string ); } + public function getFormCycleUrl(): string + { + return $this->formCycleUrl; + } + public function getAdminUrl(): string { return $this->formCycleFrontendUrl ?: $this->formCycleUrl; diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 46ba9ba..b3906ec 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -9,8 +9,9 @@ use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; +use Xima\XmFormcycle\Dto\ElementSettings; use Xima\XmFormcycle\Dto\FormcycleConfiguration; -use Xima\XmFormcycle\Dto\IntegrationMode; use Xima\XmFormcycle\Error\FormcycleConfigurationException; use Xima\XmFormcycle\Error\FormcycleConnectionException; @@ -25,7 +26,8 @@ final class FormcycleService */ public function __construct( private readonly ExtensionConfiguration $extensionConfiguration, - private readonly FrontendInterface $cache + private readonly FrontendInterface $cache, + private readonly UriBuilder $uriBuilder ) { $extConfig = $this->extensionConfiguration->get('xm_formcycle'); $this->configuration = FormcycleConfiguration::createFromExtensionConfiguration($extConfig); @@ -45,7 +47,7 @@ public function getAvailableFormConfigurationByFormId(string $formId): array { $forms = $this->getAvailableForms(); - $index = array_search((int)$formId, array_column($forms, 'form_id'), true); + $index = array_search((int)$formId, array_column($forms, 'id'), true); return $index !== false ? $forms[$index] : []; } @@ -115,8 +117,42 @@ public function getAdminUrl(): string return $this->configuration->getAdminUrl(); } - public function getIntegrationMode(): IntegrationMode + public function getIframeUrl(ElementSettings $settings): string { - return $this->configuration->getIntegrationMode(); + $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); + $params = $this->getCommonQueryParams($settings); + + return $url . '?' . http_build_query($params); + } + + private function getCommonQueryParams(ElementSettings $settings): array + { + $params = [ + 'xfc-rp-inline' => true, + 'xfc-rp-usejq' => $settings->loadFormcycleJquery ? 1 : 0, + 'xfc-rp-useui' => $settings->loadFormcycleJqueryUi ? 1 : 0, + 'xfc-rp-usebs' => $settings->loadResponseJs ? 1 : 0, + 'xfc-pp-external' => true, + 'xfc-pp-base-url' => $this->configuration->getFormCycleUrl(), + 'xfc-rp-keepalive' => true, + ]; + + if ($settings->successPid) { + $url = $this->uriBuilder + ->setTargetPageUid($settings->successPid) + ->setCreateAbsoluteUri(true) + ->build(); + $params['xfc-pp-success-url'] = $url; + } + + if ($settings->errorPid) { + $url = $this->uriBuilder + ->setTargetPageUid($settings->errorPid) + ->setCreateAbsoluteUri(true) + ->build(); + $params['xfc-pp-error-url'] = $url; + } + + return $params; } } diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index d69f203..c9ab8a2 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -36,7 +36,7 @@


diff --git a/Resources/Public/JavaScript/Frontend/FormcycleIframe.js b/Resources/Public/JavaScript/Frontend/FormcycleIframe.js index 371fdfb..11ec7a4 100644 --- a/Resources/Public/JavaScript/Frontend/FormcycleIframe.js +++ b/Resources/Public/JavaScript/Frontend/FormcycleIframe.js @@ -1 +1,5 @@ -console.log('hello') +window.addEventListener("message", event => { + document.querySelectorAll('iframe.xm-formcycle-iframe').forEach(iframe => { + iframe.style.height = `${event.data.height}px` + }) +}) From a1e3532b3f1f7dc040f4d8785fc3eb20deaa1c9d Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 28 Feb 2024 19:41:29 +0100 Subject: [PATCH 26/66] feat: use AbstratctProcessor to init settings --- Classes/DataProcessing/AbstractProcessor.php | 24 +++++++++++++++++-- Classes/DataProcessing/AjaxProcessor.php | 15 ++++++------ .../DataProcessing/IntegratedProcessor.php | 17 +++++++------ Classes/DataProcessing/iFrameProcessor.php | 15 ++++++------ Resources/Private/Templates/Formcycle.html | 5 ---- .../JavaScript/Frontend/FormcycleIframe.js | 8 ++++--- 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/Classes/DataProcessing/AbstractProcessor.php b/Classes/DataProcessing/AbstractProcessor.php index 6f5ab26..c2db86b 100644 --- a/Classes/DataProcessing/AbstractProcessor.php +++ b/Classes/DataProcessing/AbstractProcessor.php @@ -6,6 +6,7 @@ use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; use Xima\XmFormcycle\Dto\ElementSettings; +use Xima\XmFormcycle\Dto\IntegrationMode; use Xima\XmFormcycle\Service\FormcycleService; abstract class AbstractProcessor implements DataProcessorInterface @@ -18,11 +19,30 @@ public function __construct( ) { } - protected function initElementSettings(ContentObjectRenderer $cObj): void - { + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ) { $this->settings = ElementSettings::createFromContentElement( $this->flexFormService, $cObj, ); + + if ($this->settings->integrationMode !== $this->getIntegrationMode()) { + return $processedData; + } + + return $this->subProcess($cObj, $contentObjectConfiguration, $processorConfiguration, $processedData); } + + abstract protected function getIntegrationMode(): IntegrationMode; + + abstract public function subProcess( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ): array; } diff --git a/Classes/DataProcessing/AjaxProcessor.php b/Classes/DataProcessing/AjaxProcessor.php index e19abbb..b86b921 100644 --- a/Classes/DataProcessing/AjaxProcessor.php +++ b/Classes/DataProcessing/AjaxProcessor.php @@ -7,18 +7,17 @@ class AjaxProcessor extends AbstractProcessor { - public function process( + public function subProcess( ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData = [] - ) { - $this->initElementSettings($cObj); - - if ($this->settings->integrationMode !== IntegrationMode::Ajax) { - return $processedData; - } - + ): array { return $processedData; } + + protected function getIntegrationMode(): IntegrationMode + { + return IntegrationMode::Ajax; + } } diff --git a/Classes/DataProcessing/IntegratedProcessor.php b/Classes/DataProcessing/IntegratedProcessor.php index dce9c0b..4785142 100644 --- a/Classes/DataProcessing/IntegratedProcessor.php +++ b/Classes/DataProcessing/IntegratedProcessor.php @@ -7,18 +7,17 @@ class IntegratedProcessor extends AbstractProcessor { - public function process( + public function subProcess( ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, - ?array $processedData - ) { - $this->initElementSettings($cObj); - - if ($this->settings->integrationMode !== IntegrationMode::Integrated) { - return $processedData; - } - + array $processedData + ): array { return $processedData; } + + protected function getIntegrationMode(): IntegrationMode + { + return IntegrationMode::Integrated; + } } diff --git a/Classes/DataProcessing/iFrameProcessor.php b/Classes/DataProcessing/iFrameProcessor.php index 5407c2c..08de8a4 100644 --- a/Classes/DataProcessing/iFrameProcessor.php +++ b/Classes/DataProcessing/iFrameProcessor.php @@ -9,18 +9,12 @@ class iFrameProcessor extends AbstractProcessor { - public function process( + public function subProcess( ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData - ) { - $this->initElementSettings($cObj); - - if ($this->settings->integrationMode !== IntegrationMode::Iframe) { - return $processedData; - } - + ): array { /** @var PageRenderer $pageRenderer */ $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/FormcycleIframe.js'); @@ -30,4 +24,9 @@ public function process( return $processedData; } + + protected function getIntegrationMode(): IntegrationMode + { + return IntegrationMode::Iframe; + } } diff --git a/Resources/Private/Templates/Formcycle.html b/Resources/Private/Templates/Formcycle.html index 842a479..fec146f 100644 --- a/Resources/Private/Templates/Formcycle.html +++ b/Resources/Private/Templates/Formcycle.html @@ -4,11 +4,6 @@
- FORM! - - {data.tx_xmformcycle_form_id} - - diff --git a/Resources/Public/JavaScript/Frontend/FormcycleIframe.js b/Resources/Public/JavaScript/Frontend/FormcycleIframe.js index 11ec7a4..2941647 100644 --- a/Resources/Public/JavaScript/Frontend/FormcycleIframe.js +++ b/Resources/Public/JavaScript/Frontend/FormcycleIframe.js @@ -1,5 +1,7 @@ window.addEventListener("message", event => { - document.querySelectorAll('iframe.xm-formcycle-iframe').forEach(iframe => { - iframe.style.height = `${event.data.height}px` - }) + const iframeUrl = event.data.url + const iframeElement = document.querySelector(`iframe.xm-formcycle-iframe[src="${iframeUrl}"]`) + if (iframeElement) { + iframeElement.style.height = `${event.data.height}px` + } }) From fe6e8811ebba435ae2433136aad5c56ea2098c6c Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 08:57:57 +0100 Subject: [PATCH 27/66] feat: support all ajax integration modes --- Classes/DataProcessing/AbstractProcessor.php | 3 ++- Classes/DataProcessing/AjaxProcessor.php | 9 +++++++++ Classes/Dto/IntegrationMode.php | 10 ++++++++++ Classes/Service/FormcycleService.php | 18 ++++++++++++++++++ Resources/Private/Templates/Formcycle.html | 4 ++++ .../JavaScript/Frontend/FormcycleIAjax.js | 4 ++++ 6 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 Resources/Public/JavaScript/Frontend/FormcycleIAjax.js diff --git a/Classes/DataProcessing/AbstractProcessor.php b/Classes/DataProcessing/AbstractProcessor.php index c2db86b..1273c43 100644 --- a/Classes/DataProcessing/AbstractProcessor.php +++ b/Classes/DataProcessing/AbstractProcessor.php @@ -30,7 +30,8 @@ public function process( $cObj, ); - if ($this->settings->integrationMode !== $this->getIntegrationMode()) { + $currentIntegrationMode = $this->settings->integrationMode ?? $this->formcycleService->getDefaultIntegrationMode(); + if ($currentIntegrationMode->forDataProcessing() !== $this->getIntegrationMode()) { return $processedData; } diff --git a/Classes/DataProcessing/AjaxProcessor.php b/Classes/DataProcessing/AjaxProcessor.php index b86b921..8809b8c 100644 --- a/Classes/DataProcessing/AjaxProcessor.php +++ b/Classes/DataProcessing/AjaxProcessor.php @@ -2,6 +2,8 @@ namespace Xima\XmFormcycle\DataProcessing; +use TYPO3\CMS\Core\Page\PageRenderer; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; use Xima\XmFormcycle\Dto\IntegrationMode; @@ -13,6 +15,13 @@ public function subProcess( array $processorConfiguration, array $processedData = [] ): array { + /** @var PageRenderer $pageRenderer */ + $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/FormcycleAjax.js'); + + $processedData['ajax'] = []; + $processedData['ajax']['url'] = $this->formcycleService->getAjaxUrl($this->settings); + return $processedData; } diff --git a/Classes/Dto/IntegrationMode.php b/Classes/Dto/IntegrationMode.php index b99d162..6a61be3 100644 --- a/Classes/Dto/IntegrationMode.php +++ b/Classes/Dto/IntegrationMode.php @@ -7,4 +7,14 @@ enum IntegrationMode: string case Integrated = 'integrated'; case Ajax = 'AJAX'; case Iframe = 'iFrame'; + case AjaxTypo3 = 'AJAX (TYPO3)'; + case AjaxFormcycle = 'AJAX (FORMCYCLE)'; + + public function forDataProcessing(): self + { + if ($this === self::AjaxTypo3 || $this === self::AjaxFormcycle) { + return self::Ajax; + } + return $this; + } } diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index b3906ec..debfb96 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -12,6 +12,7 @@ use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; use Xima\XmFormcycle\Dto\ElementSettings; use Xima\XmFormcycle\Dto\FormcycleConfiguration; +use Xima\XmFormcycle\Dto\IntegrationMode; use Xima\XmFormcycle\Error\FormcycleConfigurationException; use Xima\XmFormcycle\Error\FormcycleConnectionException; @@ -120,7 +121,19 @@ public function getAdminUrl(): string public function getIframeUrl(ElementSettings $settings): string { $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); + + $params = $this->getCommonQueryParams($settings); + $params['xfc-height-changed-evt'] = true; + + return $url . '?' . http_build_query($params); + } + + public function getAjaxUrl(ElementSettings $settings): string + { + $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); + $params = $this->getCommonQueryParams($settings); + $params['xfc-rp-form-only'] = true; return $url . '?' . http_build_query($params); } @@ -155,4 +168,9 @@ private function getCommonQueryParams(ElementSettings $settings): array return $params; } + + public function getDefaultIntegrationMode(): IntegrationMode + { + return $this->configuration->getIntegrationMode(); + } } diff --git a/Resources/Private/Templates/Formcycle.html b/Resources/Private/Templates/Formcycle.html index fec146f..dfa191f 100644 --- a/Resources/Private/Templates/Formcycle.html +++ b/Resources/Private/Templates/Formcycle.html @@ -8,6 +8,10 @@ + +
+
+
diff --git a/Resources/Public/JavaScript/Frontend/FormcycleIAjax.js b/Resources/Public/JavaScript/Frontend/FormcycleIAjax.js new file mode 100644 index 0000000..9dbfe52 --- /dev/null +++ b/Resources/Public/JavaScript/Frontend/FormcycleIAjax.js @@ -0,0 +1,4 @@ +document.querySelectorAll('.xm-formcycle-ajax').forEach(div => { + const url = div.getAttribute('data-ajax') + console.log(url) +}) From 822f720b3cfd01766b2b84f52de6b84abba138df Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 10:43:14 +0100 Subject: [PATCH 28/66] feat: include jquery, stop requesting jquery in ajax requests --- Classes/DataProcessing/AjaxProcessor.php | 5 ++++- Classes/Service/FormcycleService.php | 1 + .../Public/JavaScript/Frontend/FormcycleAjax.js | 13 +++++++++++++ .../Public/JavaScript/Frontend/FormcycleIAjax.js | 4 ---- .../Public/JavaScript/Frontend/jquery-3.7.1.min.js | 2 ++ 5 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 Resources/Public/JavaScript/Frontend/FormcycleAjax.js delete mode 100644 Resources/Public/JavaScript/Frontend/FormcycleIAjax.js create mode 100644 Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js diff --git a/Classes/DataProcessing/AjaxProcessor.php b/Classes/DataProcessing/AjaxProcessor.php index 8809b8c..1f4abf3 100644 --- a/Classes/DataProcessing/AjaxProcessor.php +++ b/Classes/DataProcessing/AjaxProcessor.php @@ -13,10 +13,13 @@ public function subProcess( ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, - array $processedData = [] + array $processedData ): array { /** @var PageRenderer $pageRenderer */ $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + if ($this->settings->loadFormcycleJquery) { + $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js'); + } $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/FormcycleAjax.js'); $processedData['ajax'] = []; diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index debfb96..a26bce1 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -134,6 +134,7 @@ public function getAjaxUrl(ElementSettings $settings): string $params = $this->getCommonQueryParams($settings); $params['xfc-rp-form-only'] = true; + $params['xfc-rp-usejq'] = 0; return $url . '?' . http_build_query($params); } diff --git a/Resources/Public/JavaScript/Frontend/FormcycleAjax.js b/Resources/Public/JavaScript/Frontend/FormcycleAjax.js new file mode 100644 index 0000000..593b55a --- /dev/null +++ b/Resources/Public/JavaScript/Frontend/FormcycleAjax.js @@ -0,0 +1,13 @@ +for (const div of document.querySelectorAll('.xm-formcycle-ajax')) { + const url = div.getAttribute('data-ajax-url') + + $.ajax({ + url: url, + dataType: 'html', + xhrFields: { + withCredentials: true + } + }).done(data => { + $(div).html(data) + }) +} diff --git a/Resources/Public/JavaScript/Frontend/FormcycleIAjax.js b/Resources/Public/JavaScript/Frontend/FormcycleIAjax.js deleted file mode 100644 index 9dbfe52..0000000 --- a/Resources/Public/JavaScript/Frontend/FormcycleIAjax.js +++ /dev/null @@ -1,4 +0,0 @@ -document.querySelectorAll('.xm-formcycle-ajax').forEach(div => { - const url = div.getAttribute('data-ajax') - console.log(url) -}) diff --git a/Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js b/Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js new file mode 100644 index 0000000..7f37b5d --- /dev/null +++ b/Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="
",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 Date: Thu, 29 Feb 2024 11:51:25 +0100 Subject: [PATCH 29/66] feat: add integrated mode --- .../DataProcessing/IntegratedProcessor.php | 5 +++ Classes/Service/FormcycleService.php | 37 +++++++++++++------ Resources/Private/Templates/Formcycle.html | 4 ++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Classes/DataProcessing/IntegratedProcessor.php b/Classes/DataProcessing/IntegratedProcessor.php index 4785142..1cb8921 100644 --- a/Classes/DataProcessing/IntegratedProcessor.php +++ b/Classes/DataProcessing/IntegratedProcessor.php @@ -13,6 +13,11 @@ public function subProcess( array $processorConfiguration, array $processedData ): array { + $form = $this->formcycleService->getFormHtml($this->settings); + + $processedData['form'] = []; + $processedData['form']['html'] = $form; + return $processedData; } diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index a26bce1..06bf29a 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -128,17 +128,6 @@ public function getIframeUrl(ElementSettings $settings): string return $url . '?' . http_build_query($params); } - public function getAjaxUrl(ElementSettings $settings): string - { - $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); - - $params = $this->getCommonQueryParams($settings); - $params['xfc-rp-form-only'] = true; - $params['xfc-rp-usejq'] = 0; - - return $url . '?' . http_build_query($params); - } - private function getCommonQueryParams(ElementSettings $settings): array { $params = [ @@ -170,8 +159,34 @@ private function getCommonQueryParams(ElementSettings $settings): array return $params; } + public function getAjaxUrl(ElementSettings $settings): string + { + $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); + + $params = $this->getCommonQueryParams($settings); + $params['xfc-rp-form-only'] = true; + $params['xfc-rp-usejq'] = 0; + + return $url . '?' . http_build_query($params); + } + public function getDefaultIntegrationMode(): IntegrationMode { return $this->configuration->getIntegrationMode(); } + + public function getFormHtml(ElementSettings $settings): string + { + $url = $this->getIntegratedFormUrl($settings); + return GeneralUtility::getUrl($url) ?: ''; + } + + public function getIntegratedFormUrl(ElementSettings $settings): string + { + $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); + + $params = $this->getCommonQueryParams($settings); + + return $url . '?' . http_build_query($params); + } } diff --git a/Resources/Private/Templates/Formcycle.html b/Resources/Private/Templates/Formcycle.html index dfa191f..5c0587c 100644 --- a/Resources/Private/Templates/Formcycle.html +++ b/Resources/Private/Templates/Formcycle.html @@ -12,6 +12,10 @@
+ + {form.html->f:format.raw()} + +

From adb7b044684e0328b95aa184530021cc1d62c621 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 12:10:57 +0100 Subject: [PATCH 30/66] feat: add middleware for loading via ajax --- Classes/Middleware/FormMiddleware.php | 45 +++++++++++++++++++++++ Classes/Service/FormcycleService.php | 4 ++ Configuration/RequestMiddlewares.php | 12 ++++++ Configuration/TypoScript/setup.typoscript | 23 ------------ 4 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 Classes/Middleware/FormMiddleware.php create mode 100644 Configuration/RequestMiddlewares.php diff --git a/Classes/Middleware/FormMiddleware.php b/Classes/Middleware/FormMiddleware.php new file mode 100644 index 0000000..fcb65fb --- /dev/null +++ b/Classes/Middleware/FormMiddleware.php @@ -0,0 +1,45 @@ +getAttribute('routing'); + if ($pageArguments->getPageType() !== '1464705954') { + return $handler->handle($request); + } + + $params = $request->getQueryParams(); + if (!isset($params['formId'])) { + return $this->responseFactory->createResponse(400); + } + + $settings = new ElementSettings(); + $settings->formId = $params['formId']; + + $html = $this->formcycleService->getFormHtml($settings); + + $response = $this->responseFactory->createResponse(); + $response->getBody()->write($html); + + return $response; + } +} diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 06bf29a..5e675bb 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -161,6 +161,10 @@ private function getCommonQueryParams(ElementSettings $settings): array public function getAjaxUrl(ElementSettings $settings): string { + if ($settings->integrationMode === IntegrationMode::AjaxTypo3) { + return '?type=1464705954&formId=' . $settings->formId; + } + $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); $params = $this->getCommonQueryParams($settings); diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php new file mode 100644 index 0000000..6fd2bdf --- /dev/null +++ b/Configuration/RequestMiddlewares.php @@ -0,0 +1,12 @@ + [ + 'xm-formcycle/form' => [ + 'target' => \Xima\XmFormcycle\Middleware\FormMiddleware::class, + 'before' => [ + 'typo3/cms-frontend/tsfe', + ], + ], + ], +]; diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index e7ea23a..6ab3403 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -49,29 +49,6 @@ plugin.tx_xmformcycle._CSS_DEFAULT_STYLE ( page.includeJSFooter.tx_xmformcycle = EXT:xm_formcycle/Resources/Public/Js/xm_formcycle.js -tx_xmformcycle_ajax = PAGE -tx_xmformcycle_ajax { - typeNum = 1464705954 - config { - disableAllHeaderCode = 1 - additionalHeaders = Content-type:text/html - xhtml_cleaning = 0 - admPanel = 0 - debug = 0 - no_cache = 1 - } - - 10 = USER - 10 { - userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run - extensionName = XmFormcycle - pluginName = Xmformcycle - vendorName = Xima - controller = Formcycle - settings =< plugin.tx_xmformcycle.settings - } -} - tt_content.formcycle =< lib.contentElement tt_content.formcycle { templateRootPaths.0 = EXT:xm_formcycle/Resources/Private/Templates/ From 631821a45540f09855073bb979514e022b6330b5 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 12:14:44 +0100 Subject: [PATCH 31/66] feat: remove old files --- Classes/Controller/FormcycleController.php | 254 ------------------ Classes/Form/Element/FcElement.php | 34 --- Classes/Form/Element/StartNewElement.php | 110 -------- Classes/Form/Element/StartOpenElement.php | 31 --- Classes/Helper/FcHelper.php | 209 -------------- Classes/Helper/WorkaroundHelper.php | 47 ---- Resources/Private/Layouts/Default.html | 3 - .../Private/Partials/Formcycle/AJAX.html | 25 -- .../Private/Partials/Formcycle/iFrame.html | 13 - .../Partials/Formcycle/integrated.html | 1 - .../Templates/Formcycle/FormContent.html | 1 - .../Private/Templates/Formcycle/List.html | 5 - Resources/Public/Js/xm_formcycle.js | 29 -- ext_icon.png | Bin 1280 -> 0 bytes ext_tables.php | 5 - 15 files changed, 767 deletions(-) delete mode 100644 Classes/Controller/FormcycleController.php delete mode 100644 Classes/Form/Element/FcElement.php delete mode 100644 Classes/Form/Element/StartNewElement.php delete mode 100644 Classes/Form/Element/StartOpenElement.php delete mode 100644 Classes/Helper/FcHelper.php delete mode 100644 Classes/Helper/WorkaroundHelper.php delete mode 100644 Resources/Private/Layouts/Default.html delete mode 100644 Resources/Private/Partials/Formcycle/AJAX.html delete mode 100644 Resources/Private/Partials/Formcycle/iFrame.html delete mode 100644 Resources/Private/Partials/Formcycle/integrated.html delete mode 100644 Resources/Private/Templates/Formcycle/FormContent.html delete mode 100644 Resources/Private/Templates/Formcycle/List.html delete mode 100644 Resources/Public/Js/xm_formcycle.js delete mode 100644 ext_icon.png delete mode 100644 ext_tables.php diff --git a/Classes/Controller/FormcycleController.php b/Classes/Controller/FormcycleController.php deleted file mode 100644 index 9e80eed..0000000 --- a/Classes/Controller/FormcycleController.php +++ /dev/null @@ -1,254 +0,0 @@ -extConf = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$this->extKey]; - } - - /** - * action list - */ - public function listAction(): ResponseInterface - { - $viewVars = []; - - $typo3link = false; - $formcycleServerUrl = ''; - $integrationMode = $this->extConf['integrationMode']; - - if (array_key_exists('integrationMode', $this->settings['xf']) - && $this->settings['xf']['integrationMode'] != 'default' - ) { - $integrationMode = $this->settings['xf']['integrationMode']; - } - - switch ($integrationMode) { - case 'AJAX (TYPO3)': - $viewVars = $this->getByAjax(); - $partialsTemplate = 'AJAX'; - $typo3link = true; - break; - case 'AJAX (FORMCYCLE)': - $partialsTemplate = 'AJAX'; - $formcycleServerUrl = $this->getFcUrl(new FcHelper(true), '&xfc-rp-form-only=true'); - break; - case 'iFrame': - $partialsTemplate = 'iFrame'; - $formcycleServerUrl = $this->getFcUrl(new FcHelper(true)) . '&xfc-height-changed-evt=true'; - break; - case 'integrated': - default: - $viewVars = $this->getDirectly(false); - $partialsTemplate = 'integrated'; - } - - $viewVars = array_merge($viewVars, [ - 'typo3link' => $typo3link, - 'formcycleServerUrl' => $formcycleServerUrl, - 'partialsTemplate' => $partialsTemplate, - 'integrationModeKey' => $this->extConf['integrationMode'], - ]); - - $this->view->assignMultiple($viewVars); - return $this->htmlResponse(); - } - - /** - * Initialize filter action - */ - public function initializeFormContentAction() - { - /** @var WorkaroundHelper $workarounds */ - $workarounds = $this->objectManager->get(WorkaroundHelper::class); - $args = $this->request->getArguments(); - - $this->settings = array_merge( - $this->settings, - $workarounds->findFlexformDataByUid($args['uid']) - ); - } - - public function formContentAction(): ResponseInterface - { - $this->view->assignMultiple($this->getDirectly(true)); - return $this->htmlResponse(); - } - - /** - * @param bool $frontendServerUrl - * @return array - */ - protected function getDirectly($frontendServerUrl = false) - { - $fch = new FcHelper($frontendServerUrl); - $fc_ContentUrl = $this->getFcUrl($fch, '&xfc-rp-form-only=true'); - - return [ - 'form' => $fch->getFileContent($fc_ContentUrl, '', '', ''), - ]; - } - - /** - * @param string $fcParams - * @return string - */ - protected function getFcUrl(FcHelper $fch, $fcParams = '') - { - $GLOBALS['icss'] = $this->settings['xf']['icss']; - $selErrorPage = $this->getRedirectURL($this->settings['xf']['siteerror']); - $selOkPage = $this->getRedirectURL($this->settings['xf']['siteok']); - $usejq = $this->settings['xf']['useFcjQuery']; - $useui = $this->settings['xf']['useFcjQueryUi']; - $usebs = $this->settings['xf']['useFcBootStrap']; - $selProjectId = $this->settings['xf']['xfc_p_id']; - $frontendLang = $GLOBALS['TSFE']->getLanguage()->getTwoLetterIsoCode() ?: 'de'; - $fcParams .= $this->settings['xf']['useFcUrlParams']; - - $fcParams = $this->resolveCustomParameters($fcParams); - - $fc_ContentUrl = $fch->getFormContent( - $selProjectId, - $selOkPage, - $selErrorPage, - $usejq, - $useui, - $usebs, - $frontendLang, - $fcParams - ); - - return $fc_ContentUrl; - } - - /** - * Process to load form by AJAX - * - * @return array - */ - protected function getByAjax() - { - $cObj = $this->configurationManager->getContentObject(); - - return [ - 'uid' => $cObj->data['uid'], - ]; - } - - /** - * @param $uid - * @return string - */ - public function getRedirectURL($uid) - { - return $this->uriBuilder - ->reset() - ->setArguments(['L' => GeneralUtility::makeInstance(Context::class)->getAspect('language')]) - ->setTargetPageUid((int)$uid) - ->setCreateAbsoluteUri(true) - ->build(); - } - - /** - * @param $s - * @param bool|false $use_forwarded_host - * @return string - */ - public function url_origin($s, $use_forwarded_host = false) - { - $ssl = (!empty($s['HTTPS']) && $s['HTTPS'] == 'on') ? true : false; - $sp = strtolower((string)$s['SERVER_PROTOCOL']); - $protocol = substr($sp, 0, strpos($sp, '/')) . (($ssl) ? 's' : ''); - $port = $s['SERVER_PORT']; - $port = ((!$ssl && $port == '80') || ($ssl && $port == '443')) ? '' : ':' . $port; - $host = ($use_forwarded_host && isset($s['HTTP_X_FORWARDED_HOST'])) ? $s['HTTP_X_FORWARDED_HOST'] : ($s['HTTP_HOST'] ?? null); - $host ??= $s['SERVER_NAME'] . $port; - return $protocol . '://' . $host; - } - - /** - * @param $s - * @param bool|false $use_forwarded_host - * @return string - */ - public function full_url($s, $use_forwarded_host = false) - { - return $this->url_origin($s, $use_forwarded_host) . strtok($s['REQUEST_URI'], '?'); - } - - /** - * @return string - */ - private function resolveCustomParameters(string $fcParams) - { - $result = $fcParams; - - if (preg_match_all('~(?:{|%7B)(?[\d\w]+)(?:}|%7D)~', $fcParams, $matches) !== false) { - foreach ($matches['params'] as $idx => $param) { - if (array_key_exists($param, $_GET)) { - $result = str_replace( - $matches[0][$idx], - strip_tags((string)($_GET[$param])), - $result - ); - } else { - $result = str_replace( - $matches[0][$idx], - '', - $result - ); - } - } - } - - return $result; - } -} diff --git a/Classes/Form/Element/FcElement.php b/Classes/Form/Element/FcElement.php deleted file mode 100644 index e764685..0000000 --- a/Classes/Form/Element/FcElement.php +++ /dev/null @@ -1,34 +0,0 @@ -data, for example the above - // parameters are available in $this->data['parameterArray']['fieldConf']['config']['parameters'] - - $fch = new FcHelper(); - $fc_AdminUrl = $fch->getFcAdministrationUrl(); - $user_lang = $GLOBALS['BE_USER']->uc['lang']; - $mssage_style = '
'; - $mssage_link_style = '
'; - $message_link = $mssage_link_style . 'Open FormCycle Administration'; - $message = $mssage_style . 'Link opens in a new window with a seperat login dialog.
'; - - if ($user_lang == 'de') { - $message_link = $mssage_link_style . 'FormCycle Administration öffnen'; - $message = $mssage_style . 'Der Link öffnet ein neues Fenster mit eigenen Login-Dialog.'; - } - - $result = $this->initializeResultArray(); - $result['html'] = '  ' . $message_link . '
  ' . $message . '

'; - return $result; - } -} diff --git a/Classes/Form/Element/StartNewElement.php b/Classes/Form/Element/StartNewElement.php deleted file mode 100644 index d9d0de7..0000000 --- a/Classes/Form/Element/StartNewElement.php +++ /dev/null @@ -1,110 +0,0 @@ -data, for example the above - // parameters are available in $this->data['parameterArray']['fieldConf']['config']['parameters'] - $PA = $this->data; - - //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($PA); - - $fch = new FcHelper(); - $fc_iFrameUrl = $fch->getFcIframeUrl(); - - $user_lang = $GLOBALS['BE_USER']->uc['lang']; - $user_name = $GLOBALS['gFcUser']; - $user_pass = $GLOBALS['gFcPass']; - - $selProjectId = $this->settings['xf']['xfc_p_id']; - - if (is_array($PA['databaseRow']['pi_flexform']) && array_key_exists( - 'data', - $PA['databaseRow']['pi_flexform'] - )) { - $pid = $PA['databaseRow']['pi_flexform']['data']['sheetGeneralOptions']['lDEF']['settings.xf.xfc_p_id']['vDEF']; - } else { - $xml = new SimpleXMLElement($PA['databaseRow']['pi_flexform']); - $pid = $xml->data->sheet[0]->language->field->value->__toString(); - } - - $retTemp = ' - - - - '; - - $result = $this->initializeResultArray(); - $result['html'] = $retTemp; - return $result; - } -} diff --git a/Classes/Form/Element/StartOpenElement.php b/Classes/Form/Element/StartOpenElement.php deleted file mode 100644 index 37b33ea..0000000 --- a/Classes/Form/Element/StartOpenElement.php +++ /dev/null @@ -1,31 +0,0 @@ -data, for example the above - // parameters are available in $this->data['parameterArray']['fieldConf']['config']['parameters'] - - $user_lang = $GLOBALS['BE_USER']->uc['lang']; - - $mssage_style = '
'; - $mssage_link_style = '
'; - $message_link = $mssage_link_style . 'FormCycle TYPO3 extension help'; - - if ($user_lang == 'de') { - $message_link = $mssage_link_style . 'Hilfe für FormCycle TYPO3 Erweiterung öffnen
'; - } - $fc_HelpUrl = 'http://help.formcycle.eu/xwiki/bin/view/CMS+Extension/Typo3+Extension'; - - $result = $this->initializeResultArray(); - $result['html'] = '  ' . $message_link . '
'; - return $result; - } -} diff --git a/Classes/Helper/FcHelper.php b/Classes/Helper/FcHelper.php deleted file mode 100644 index 96d722e..0000000 --- a/Classes/Helper/FcHelper.php +++ /dev/null @@ -1,209 +0,0 @@ -extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get($ek); - $GLOBALS['gFcUrl'] = ($frontendServerUrl && $this->extConf['formCycleFrontendUrl'] != '') ? $this->extConf['formCycleFrontendUrl'] : $this->extConf['formCycleUrl']; - $GLOBALS['gFcUser'] = $this->extConf['formCycleUser']; - $GLOBALS['gFcPass'] = $this->extConf['formCyclePass']; - } - - /** - * @return array - */ - public function getCurlInfos() - { - return $this->curlInfos; - } - - /** - * @return array - */ - public function getCurlErrors() - { - return $this->curlErrors; - } - - /** - * @return mixed - */ - public function getIcss() - { - return $GLOBALS['icss']; - } - - /** - * @param $myURL - * @param $myAction - * @param $myUser - * @param $myPasswd - * @return mixed|string - */ - public function getFileContent($myURL, $myAction, $myUser, $myPasswd) - { - $curlPostfields = ''; - - if (function_exists('curl_init')) { - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_URL, $myURL); - curl_setopt($ch, CURLOPT_POSTFIELDS, $curlPostfields); - - // Allow follow redirects - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, static::CURL_OPTION_MAXREDIRS); - curl_setopt($ch, CURLOPT_USERAGENT, static::CURL_OPTION_USERAGENT); - - if ($myAction == 'version') { - curl_setopt($ch, CURLOPT_POST, false); - curl_setopt($ch, CURLOPT_HTTPGET, true); - } else { - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_HTTPGET, false); - } - - if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']) { - curl_setopt($ch, CURLOPT_PROXY, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer']); - - if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel']) { - curl_setopt( - $ch, - CURLOPT_HTTPPROXYTUNNEL, - $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyTunnel'] - ); - } - if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']) { - curl_setopt($ch, CURLOPT_PROXYUSERPWD, $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass']); - } - } - - $result = curl_exec($ch); - $this->curlInfos[] = curl_getinfo($ch); - if (curl_errno($ch)) { - $this->curlErrors[] = curl_error($ch); - } - - curl_close($ch); - } else { - $httpArray = [ - 'method' => 'POST', - 'request_fulluri' => true, - ]; - $opts = [ - 'http' => $httpArray, - 'https' => $httpArray, - ]; - $context = stream_context_create($opts); - $result = file_get_contents($myURL, false, $context); - } - - return $result; - } - - /** - * @return mixed - */ - public function getTypoSiteURL() - { - return GeneralUtility::getIndpEnv('TYPO3_SITE_URL'); - } - - /** - * @param $projektId - * @param $siteok - * @param $siteerror - * @param $usejq - * @param $useui - * @param $usebs - * @param $frontendLang - * @param $fcParams - * @return string - */ - public function getFormContent( - $projektId, - $siteok, - $siteerror, - $usejq, - $useui, - $usebs, - $frontendLang, - $fcParams - ) { - $okUrl = $siteok; - $errorUrl = $siteerror; - $sessionID = $GLOBALS['TSFE']->fe_user->id; - - $GLOBALS['gFcUrl'] = trim((string)$GLOBALS['gFcUrl'], " /\t\n\r\0\x0B"); - $GLOBALS['gFcUrl'] .= '/'; - - if (preg_match('~&lang=([^&]+)~', (string)$fcParams, $matches) === 1) { - $frontendLang = $matches[1]; - $fcParams = str_replace($matches[0], '', (string)$fcParams); - } - - return $GLOBALS['gFcUrl'] . 'form/provide/' . $projektId . - '?xfc-rp-usejq=' . $usejq . - '&xfc-rp-useui=' . $useui . - '&xfc-rp-usebs=' . $usebs . - '&xfc-rp-inline=true' . - '&lang=' . $frontendLang . - '&xfc-pp-external=true' . - '&xfc-pp-base-url=' . $GLOBALS['gFcUrl'] . - '&xfc-pp-success-url=' . $okUrl . - '&xfc-pp-error-url=' . $errorUrl . - '&xfc-rp-keepalive=true' . - $fcParams; - } - - /** - * @return string - */ - public function getFcIframeUrl() - { - return $GLOBALS['gFcUrl'] . '/external'; - } - - /** - * @return mixed - */ - public function getFcAdministrationUrl() - { - return $GLOBALS['gFcUrl']; - } - } -} diff --git a/Classes/Helper/WorkaroundHelper.php b/Classes/Helper/WorkaroundHelper.php deleted file mode 100644 index 7d1046e..0000000 --- a/Classes/Helper/WorkaroundHelper.php +++ /dev/null @@ -1,47 +0,0 @@ -createQuery(); - $query->statement('SELECT pi_flexform from tt_content where list_type="xmformcycle_xmformcycle" and uid = ' . $uid); - $pages = $query->execute(true); - $xml = simplexml_load_string((string)$pages[0]['pi_flexform']); - $flexformData = []; - - foreach ($xml->data->sheet as $sheet) { - foreach ($sheet->language->field as $field) { - $flexformData['xf'][str_replace( - 'settings.xf.', - '', - (string)$field->attributes() - )] = (string)$field->value; - } - } - - return $flexformData; - } -} diff --git a/Resources/Private/Layouts/Default.html b/Resources/Private/Layouts/Default.html deleted file mode 100644 index 73bbb7d..0000000 --- a/Resources/Private/Layouts/Default.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/Resources/Private/Partials/Formcycle/AJAX.html b/Resources/Private/Partials/Formcycle/AJAX.html deleted file mode 100644 index dde0a99..0000000 --- a/Resources/Private/Partials/Formcycle/AJAX.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - -
- Loading... -
- - diff --git a/Resources/Private/Partials/Formcycle/iFrame.html b/Resources/Private/Partials/Formcycle/iFrame.html deleted file mode 100644 index 12fa05d..0000000 --- a/Resources/Private/Partials/Formcycle/iFrame.html +++ /dev/null @@ -1,13 +0,0 @@ - - diff --git a/Resources/Private/Partials/Formcycle/integrated.html b/Resources/Private/Partials/Formcycle/integrated.html deleted file mode 100644 index ed99869..0000000 --- a/Resources/Private/Partials/Formcycle/integrated.html +++ /dev/null @@ -1 +0,0 @@ -{form} diff --git a/Resources/Private/Templates/Formcycle/FormContent.html b/Resources/Private/Templates/Formcycle/FormContent.html deleted file mode 100644 index ed99869..0000000 --- a/Resources/Private/Templates/Formcycle/FormContent.html +++ /dev/null @@ -1 +0,0 @@ -{form} diff --git a/Resources/Private/Templates/Formcycle/List.html b/Resources/Private/Templates/Formcycle/List.html deleted file mode 100644 index 6e7be1c..0000000 --- a/Resources/Private/Templates/Formcycle/List.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Resources/Public/Js/xm_formcycle.js b/Resources/Public/Js/xm_formcycle.js deleted file mode 100644 index 226f5c8..0000000 --- a/Resources/Public/Js/xm_formcycle.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * XmFormcycle - * Inhalte nachladen - */ -(function(window, document, $, undefined){ - - var $objects = {wrapper: $('.tx-xm-formcycle')}; - $objects.settings = $('#settings', $objects.wrapper); - - var $canvas = $('#' + $objects.settings.data('contentCanvas')); - - if ($objects.settings - && $objects.settings.data('contentUrl') != '' - && $canvas.length > 0) { - - $.ajax({ - url: $objects.settings.data('contentUrl'), - type: "GET", - dataType: "html", - xhrFields: { - withCredentials: true - }, - }).done(function (data) { - $canvas.html(data); - }); - } - - -})(window, document, jQuery); diff --git a/ext_icon.png b/ext_icon.png deleted file mode 100644 index 97e22106acbd3e0e9ef69f002f35d8acd65eacae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1280 zcmeAS@N?(olHy`uVBq!ia0vp^{2!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@pr;JNj1^1m z%NQ6KPi2NgltlRYSS9D@>LsS+C#C9D0BS3v*iS0onb8|oS8W7AZUnTBv4iY|~0L>t&w z5X)?UL1X1yl$uzQUlfv`pJP{$n3-3imzP?i0QA3}sf|9m0)#_tKqgxG7iFdby$bS> zouQ3Bh8R@6jXub?NWO;zEm#y7wsu@Vn_%%|$F)K>K#YNbX@RGUV~B=mZcwy$NTA5S zYj)4iNa|nKSd@0d^#T*Su8WY-Ar2CxkE2eYoGT?XoD$ms;xAf3*4J};* zojp^gw<`&7ALU-W@SMf-ZQ-vik_2k`tLN`MANBh8x9wsdE%+J=__&t6cR#+S?al0Y z^TMh#iprO(-D7{o(;*=>LBdjrA$VWRS%G>d&K#b_Tvu(9b%Umcdl&wz{ZsNhXIj!! znH>f`(MGH)C#@PpPdcaRn&t-y2mN6+{~&yRrNq-2hf6zmE#7H-^w!ERwfTx4?r<)e z5VAvZ-t&}4hivAp*3hnZwLc$uCU{1CMfmNOPu$GQZ|&as&gJ>-xeHQ)TK=~_5{!s; z?`_}1BG0;_u1ig(PaZ=UGWrhmV&)9uG1_s%u_2X|cb{&~;U^;f{>e+Pa{ zy>>%7Rr+k~#$QbS+SfO{vGM;s&pSf*+>C|nHXP2UGN)_5bDf@k?$p$+n^xKcxBTc) zZuhlyij!a}Gt-@z=W6jT=2U0Z{;M2MOwXM8v?M~UK0Kx z_vl|L$WzjBf1+c(;L@QD#y0jJ7k)n}_R+q(b6P<6fdiqUkENC{T<_i&@h4}?^=+N{ z*(oYQvJ+pvY>B@+Yttj|bJHF#UR=!-JWqflyLZY9HropT=RCz>cbPcW){^c?%aF2*Mal3 zyu9gg-y+kND*zF>jxEY}xw7e;b-lmrCDzyz<6c qg}qTrr~J}eX&FD^&b{KJ{}^_ilASVj%ii^%a>mou&t;ucLK6T Date: Thu, 29 Feb 2024 12:17:11 +0100 Subject: [PATCH 32/66] feat: cleanup --- .phpunit.result.cache | 1 + Configuration/TypoScript/constants.typoscript | 4 -- Configuration/TypoScript/setup.typoscript | 51 ------------------- ext_conf_template.txt | 4 -- ext_emconf.php | 37 +++++--------- ext_localconf.php | 42 --------------- 6 files changed, 13 insertions(+), 126 deletions(-) create mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..72a9cb3 --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"Xima\\XmFormcycle\\Tests\\Unit\\Service\\FormcycleServiceTest::testGroupForms":8,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testCreateFromExtensionConfiguration":8,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidFormcycleUrl":7,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidIntegrationMode":7},"times":{"Xima\\XmFormcycle\\Tests\\Unit\\Service\\FormcycleServiceTest::testGroupForms":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testCreateFromExtensionConfiguration":0.002,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidFormcycleUrl":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testMissingUsername":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testMissingPassword":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidFormcycleFrontendUrl":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidIntegrationMode":0.004,"Xima\\XmFormcycle\\Tests\\Unit\\Form\\Element\\FormcycleSelectionTest::testGroupForms":0.002,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testEmptyIntegrationMode":0}} \ No newline at end of file diff --git a/Configuration/TypoScript/constants.typoscript b/Configuration/TypoScript/constants.typoscript index 1ee46c7..2bd4a9d 100644 --- a/Configuration/TypoScript/constants.typoscript +++ b/Configuration/TypoScript/constants.typoscript @@ -4,8 +4,4 @@ plugin.tx_xmformcycle { partialRootPath = layoutRootPath = } - - settings { - enableJs = 1 - } } diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index 6ab3403..2da53c1 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -1,54 +1,3 @@ -plugin.tx_xmformcycle { - view { - templateRootPaths.10 = {$plugin.tx_xmformcycle.view.templateRootPath} - partialRootPaths.10 = {$plugin.tx_xmformcycle.view.partialRootPath} - layoutRootPaths.10 = {$plugin.tx_xmformcycle.view.layoutRootPath} - } - - persistence { - storagePid = {$plugin.tx_xmformcycle.persistence.storagePid} - } - - features { - # uncomment the following line to enable the new Property Mapper. - # rewrittenPropertyMapper = 1 - } - - settings { - enableJs = {$plugin.tx_xmformcycle.settings.enableJs} - jsFiles { - 10 = EXT:Resources/Public/Js/xm_formcycle.js - } - } -} - -plugin.tx_xmformcycle._CSS_DEFAULT_STYLE ( - textarea.f3-form-error { - background-color:#FF9F9F; - border: 1px #FF0000 solid; - } - - input.f3-form-error { - background-color:#FF9F9F; - border: 1px #FF0000 solid; - } - - .tx-xm-formcycle table { - border-collapse:separate; - border-spacing:10px; - } - - .tx-xm-formcycle table th { - font-weight:bold; - } - - .tx-xm-formcycle table td { - vertical-align:top; - } -) - -page.includeJSFooter.tx_xmformcycle = EXT:xm_formcycle/Resources/Public/Js/xm_formcycle.js - tt_content.formcycle =< lib.contentElement tt_content.formcycle { templateRootPaths.0 = EXT:xm_formcycle/Resources/Private/Templates/ diff --git a/ext_conf_template.txt b/ext_conf_template.txt index a82946c..d335f00 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -1,7 +1,3 @@ -TSConstantEditor.basic { - description =
No FormCycle Cloud account? Get here: http://www.form.cloud/

Show video: http://help.formcycle.eu/xwiki/bin/view/CMS+Extension/Typo3+Extension

Get help: http://help.formcycle.eu/xwiki/bin/view/CMS+Extension/Typo3+Extension -} - # cat=basic//100; type=string; label=XIMA FORMCYCLE Server URL (required) :e.g. https://pro.formcloud.de/formcycle formCycleUrl = diff --git a/ext_emconf.php b/ext_emconf.php index a513b63..289d1ed 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -1,32 +1,19 @@ 'FORMCYCLE', - 'description' => 'form management with professional form designer, process management, inbox and more features.', - 'category' => 'plugin', - 'version' => '8.0.1', - 'state' => 'stable', - 'author' => 'XIMA MEDIA GmbH', - 'author_email' => 'support@formcycle.de', - 'author_company' => 'XIMA MEDIA GmbH', - 'constraints' => [ - 'depends' => [ - 'extbase' => '1.3-', - 'fluid' => '1.3-', - 'typo3' => '9.5.0-11.5.99', + 'title' => 'formcylce', + 'description' => 'Form management with professional form designer, process management, inbox and more.', + 'category' => 'plugin', + 'version' => '9.0.0', + 'state' => 'stable', + 'author' => 'Maik Schneider', + 'author_email' => 'maik.schneider@xima.de', + 'author_company' => 'XIMA Media GmbH', + 'constraints' => [ + 'depends' => [ + 'typo3' => '12.0.0-12.5.99', ], 'conflicts' => [], - 'suggets' => [], + 'suggets' => [], ], ]; diff --git a/ext_localconf.php b/ext_localconf.php index fe034bb..46778ea 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -4,48 +4,6 @@ die('Access denied.'); } -$extConf = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['xm_formcycle']; - -if (\TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getNumericTypo3Version()) > 10000000) { - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( - 'XmFormcycle', - 'Xmformcycle', - [ - \Xima\XmFormcycle\Controller\FormcycleController::class => 'list, formContent', - ], - // non-cacheable actions - $extConf['integrationMode'] == 'integrated' ? [\Xima\XmFormcycle\Controller\FormcycleController::class => 'list'] : [] - ); -} else { - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( - 'XmFormcycle', - 'Xmformcycle', - [ - \Xima\XmFormcycle\Controller\FormcycleController::class => 'list, formContent', - ], - // non-cacheable actions - $extConf['integrationMode'] == 'integrated' ? ['Formcycle' => 'list'] : [] - ); -} - -$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1593170596] = [ - 'nodeName' => 'startNewElement', - 'priority' => 40, - 'class' => \Xima\XmFormcycle\Form\Element\StartNewElement::class, -]; - -$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1593173971] = [ - 'nodeName' => 'startOpenElement', - 'priority' => 40, - 'class' => \Xima\XmFormcycle\Form\Element\StartOpenElement::class, -]; - -$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1593174782] = [ - 'nodeName' => 'fcElement', - 'priority' => 40, - 'class' => \Xima\XmFormcycle\Form\Element\FcElement::class, -]; - // register custom form element $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1709039883] = [ 'nodeName' => 'formcycle-selection', From 111b995f2aa52c2eab0b99484fee84d19d1843c0 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 14:37:08 +0100 Subject: [PATCH 33/66] fix: remove .phpunit.result.cache --- .phpunit.result.cache | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 72a9cb3..0000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":{"Xima\\XmFormcycle\\Tests\\Unit\\Service\\FormcycleServiceTest::testGroupForms":8,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testCreateFromExtensionConfiguration":8,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidFormcycleUrl":7,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidIntegrationMode":7},"times":{"Xima\\XmFormcycle\\Tests\\Unit\\Service\\FormcycleServiceTest::testGroupForms":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testCreateFromExtensionConfiguration":0.002,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidFormcycleUrl":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testMissingUsername":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testMissingPassword":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidFormcycleFrontendUrl":0,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testInvalidIntegrationMode":0.004,"Xima\\XmFormcycle\\Tests\\Unit\\Form\\Element\\FormcycleSelectionTest::testGroupForms":0.002,"Xima\\XmFormcycle\\Tests\\Unit\\Dto\\FormcycleConfigurationTest::testEmptyIntegrationMode":0}} \ No newline at end of file From 7f646fee4cec181d867614d1b6ca8ed0997a149a Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 17:30:55 +0100 Subject: [PATCH 34/66] feat: install libxml2-utils for ddev, add xml linter script to composer.json --- .ddev/config.yaml | 1 + composer.json | 1 + 2 files changed, 2 insertions(+) diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 8f81ff7..e8ca457 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -14,6 +14,7 @@ database: use_dns_when_possible: true composer_version: "2" web_environment: [] +webimage_extra_packages: [libxml2-utils] nodejs_version: "20" # Key features of DDEV's config.yaml: diff --git a/composer.json b/composer.json index 8b8029e..b179960 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,7 @@ "scripts": { "php:fixer": "php vendor/bin/php-cs-fixer --config=php-cs-fixer.php fix", "php:stan": "php vendor/bin/phpstan --generate-baseline=phpstan-baseline.neon --allow-empty-baseline", + "xml:lint": "find . -name '*.xlf' ! -path './vendor/*' ! -path './var/*' | xargs -r xmllint --schema vendor/symfony/translation/Resources/schemas/xliff-core-1.2-transitional.xsd --noout", "typoscript:lint": "php vendor/bin/typoscript-lint", "test:unit": "php vendor/bin/phpunit -c phpunit.unit.xml" }, From 5677d7ff75db3ed5076487e4e0c0b2a4132e7bb2 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 17:31:29 +0100 Subject: [PATCH 35/66] feat: update locallang, update translations, reorder flexform values --- Configuration/FlexForms/flexform_list.xml | 161 ++++++------ Resources/Private/Language/de.locallang.xlf | 258 +++++++++++++------- Resources/Private/Language/locallang.xlf | 152 ++++++------ 3 files changed, 338 insertions(+), 233 deletions(-) diff --git a/Configuration/FlexForms/flexform_list.xml b/Configuration/FlexForms/flexform_list.xml index 46970df..0a63647 100644 --- a/Configuration/FlexForms/flexform_list.xml +++ b/Configuration/FlexForms/flexform_list.xml @@ -1,18 +1,21 @@ - - 1 - - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab2_name + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.sheet.responses + array - + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.responses.success.description + group db @@ -27,7 +30,12 @@ - + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.responses.error.description + group db @@ -44,104 +52,121 @@ - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab1_name + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.sheet.include + array - + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.integration.description + select selectSingle default - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab1_select_integrationMode_default + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.integration.default + default - integrated + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.integration.integrated + integrated - AJAX (TYPO3) + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.integration.ajaxTypo3 + AJAX (TYPO3) - AJAX (FORMCYCLE) + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.integration.ajaxFormcycle + AJAX (FORMCYCLE) - iFrame + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.integration.iframe + iFrame - - - - - - - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab5_name - - array - - - - - - group - folder - css - 0 - 0 - 10 - 2 - - - - - - - check - 1 - - + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.jquery.description + + + check + 1 + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.jquery.checkbox + 1 + + + - - - - check - - + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.jqueryUi.description + + + check + 0 + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.jqueryUi.checkbox + 0 + + + - - - - check - - + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.bootstrap.description + + + check + 0 + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.bootstrap.checkbox + 0 + + + - - - - - - - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xml:tx_xmformcycle_tab6_name - - array - - + + + LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.parameters.description + input 30 @@ -150,6 +175,6 @@ - + diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf index 626436f..995fcb7 100644 --- a/Resources/Private/Language/de.locallang.xlf +++ b/Resources/Private/Language/de.locallang.xlf @@ -1,95 +1,167 @@ - - -
- Konrad Michalik - konrad.michalik@xima.de -
- - - Formcycle - FormCycle - - - XIMA® FormCycle Formularen in das TYPO3 Frontend - XIMA® FormCycle Formularen in das TYPO3 Frontend - - - Formcycle - FormCycle - - - form - Formular - - - select form (required) - Formular auswählen (Pflichtfeld) - - - edit selected form - Ausgewähltes Formular bearbeiten - - - Integration mode - Integrationsmodus - - - Default - Standard - - - script / css - Script / CSS - - - parameters - Parameter - - - parameter (example: &param1=value1&param2=value2&param3=value3) - Parameter (Beispiel: &param1=wert1&param2=wert2&param3=wert3) - - - Use available css - Vorhandenes CSS verwenden - - - Use the jQuery framework provided by FormCycle. If you uncheck this (you use your own framework), make shure your jQuery framework is initialised before this plug in. - Von FormCycle bereitgestelltes jQuery Framework verwenden. Wird diese Option abgewählt (es wird ein vorhandenes Framework verwednet), muss das vorhandene jQuery Framework vor diesem Plug In geladen werden. - - - Use the jQuery UI framework provided by FormCycle (only necessary if you use jQuery UI Components). - Von FormCycle bereitgestelltes jQuery UI Framework verwenden (nur bei Verwendung von jQuery UI Komponenten erforderlich). - - - Use the responsejs framework provided by FormCycle (only necessary if you use responsive design). - Von FormCycle bereitgestelltes responsejs Framework verwenden (nur bei Verwendung von 'responsive Design' erforderlich). - - - response pages - Antwortseiten - - - response page by successfully submited form - Antwortseite bei erfolgreichem Absenden des Formulars - - - response page when an error occurs during execution - Antwortseite bei fehlerhafter Verarbeitung der Formulardaten - - - response pages - Antwortseiten - - - new form - Neues Formular - - - xima FormCycle administration - xima FormCycle Verwaltung - - -
+ + + + + Formcycle + Formcycle + + + Formcycle + Formcycle + + + Include a XIMA® FormCycle form + Bindet ein XIMA® FormCycle Formular ein + + + Form + Formular + + + Formcycle + Formcycle + + + Form + Formular + + + Configuration error + Konfiguration Error + + + Connection error + Verbindungsfehler + + + The extension configuration "formCycleUrl" is missing or invalid + Die Extension Konfiguration "formCycleUrl" fehlt oder ist ungültig + + + The extension configuration "formCycleUser" or "formCyclePass" is missing + Die Extension Konfiguration "formCycleUser" oder "formCyclePass" fehlt + + + The extension configuration "integrationMode" is invalid. Valid options are: "integrated", "iFrame" or "AJAX". + Die Extension Konfiguration "integrationMode" ist ungültig. Gültige Optionen sind: "integrated", "iFrame" oder "AJAX". + + + The extension configuration "formCycleFrontendUrl" is invalid + Die Extension Konfiguration "formCycleFrontendUrl" ist ungültig + + + Ungrouped + Nicht gruppiert + + + Reload + Neu laden + + + Settings + Einstellungen + + + Formcycle Admin + Formcycle Admin + + + Response pages + Antwortseiten + + + Success page + Erfolgseite + + + Page to display after successful form submission + Seite, die nach erfolgreicher Formularübermittlung angezeigt wird + + + Error page + Fehlerseite + + + Response page when an error occurs during execution + Antwortseite, wenn während der Ausführung ein Fehler auftritt + + + Integration options + Einbindungsoptionen + + + Integration type + Integrationsart + + + This defines how the Formcycle form is embedded into the page + Dies definiert, wie das Formcycle-Formular in die Seite eingebettet wird + + + Default + Standard + + + Integrated + Integriert + + + AJAX (via TYPO3) + AJAX (über TYPO3) + + + AJAX (via Fromcycle) + AJAX (über Formcycle) + + + Iframe + Iframe + + + jQuery + jQuery + + + Include jQuery + jQuery einbinden + + + If you uncheck this (use your own framework), make sure your jQuery framework is initialised + Wenn Sie dies abwählen (eigenes Framework verwenden), stellen Sie sicher, dass Ihr jQuery-Framework initialisiert ist + + + jQuery UI + jQuery UI + + + Include jQuery UI + jQuery UI einbinden + + + This is only necessary if you use jQuery UI components + Dies ist nur erforderlich, wenn Sie jQuery UI-Komponenten verwenden + + + Response JS + Response JS + + + Include Response JS + Response JS einbinden + + + This is only necessary if you use responsive design + Dies ist nur erforderlich, wenn Sie responsives Design verwenden + + + Additional parameters + Zusätzliche Parameter + + + Modify the Formcycle request, e.g. &param1=value1&param2=value2 + Ändern Sie die Formcycle-Anfrage, z.B. &param1=value1&param2=value2 + + + diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index e2b464e..2b25710 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -1,17 +1,13 @@ - - -
- Konrad Michalik - konrad.michalik@xima.de -
- + + + Formcycle - - Formcycle - + + Formcycle + Include a XIMA® FormCycle form @@ -57,66 +53,78 @@ Formcycle Admin - - XIMA® FormCycle forms in TYPO3 Frontend - - - Formcycle - - - form - - - select form (required) - - - edit selected form - - - Integration mode - - - Default - - - script / css - - - parameters - - - parameter (example: &param1=value1&param2=value2&param3=value3) - - - Use available css - - - Use the jQuery framework provided by FormCycle. If you uncheck this (you use your own framework), make shure your jQuery framework is initialised before this plug in. - - - Use the jQuery UI framework provided by FormCycle (only necessary if you use jQuery UI Components). - - - Use the responsejs framework provided by FormCycle (only necessary if you use responsive design). - - - response pages - - - response page by successfully submited form - - - response page when an error occurs during execution - - - response pages - - - new form - - - xima FormCycle administration - - - + + Response pages + + + Success page + + + Page to display after successful form submission + + + Error page + + + Response page when an error occurs during execution + + + Integration options + + + Integration type + + + This defines how the Formcycle form is embedded into the page + + + Default + + + Integrated + + + AJAX (via TYPO3) + + + AJAX (via Fromcycle) + + + Iframe + + + jQuery + + + Include jQuery + + + If you uncheck this (use your own framework), make sure your jQuery framework is initialised + + + jQuery UI + + + Include jQuery UI + + + This is only necessary if you use jQuery UI components + + + Response JS + + + Include Response JS + + + This is only necessary if you use responsive design + + + Additional parameters + + + Modify the Formcycle request, e.g. &param1=value1&param2=value2 + + +
From 7f95645454f34388a257b97cbaa16395f2cc6f86 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 17:43:09 +0100 Subject: [PATCH 36/66] feat: add phpstan config --- Classes/Dto/ElementSettings.php | 2 -- phpstan-baseline.neon | 6 ++++++ phpstan.neon | 21 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon diff --git a/Classes/Dto/ElementSettings.php b/Classes/Dto/ElementSettings.php index e861d2f..722d71f 100644 --- a/Classes/Dto/ElementSettings.php +++ b/Classes/Dto/ElementSettings.php @@ -23,8 +23,6 @@ class ElementSettings public string $additionalParameters = ''; - public string $serverUri = ''; - public static function createFromContentElement( FlexFormService $flexFormService, ContentObjectRenderer $cObj, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..796bdf9 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Dead catch \\- Xima\\\\XmFormcycle\\\\Error\\\\FormcycleConfigurationException is never thrown in the try block\\.$#" + count: 1 + path: Classes/Preview/FormcyclePreviewRenderer.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..a856455 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,21 @@ +includes: + - vendor/saschaegerer/phpstan-typo3/extension.neon + - phpstan-baseline.neon + +parameters: + parallel: + maximumNumberOfProcesses: 5 + + level: 5 + + bootstrapFiles: + - vendor/autoload.php + + paths: + - . + + excludePaths: + - vendor + - var + - php-cs-fixer.php + - ext_emconf.php From 4f26253fbd9a58b0fdd09c638525fd13c1ef3fea Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Thu, 29 Feb 2024 17:48:08 +0100 Subject: [PATCH 37/66] feat: add github workflows for sca and notification --- .github/workflows/notifications.yml | 9 +++++++++ .github/workflows/test-sca.yml | 12 ++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .github/workflows/notifications.yml create mode 100644 .github/workflows/test-sca.yml diff --git a/.github/workflows/notifications.yml b/.github/workflows/notifications.yml new file mode 100644 index 0000000..a11e6ab --- /dev/null +++ b/.github/workflows/notifications.yml @@ -0,0 +1,9 @@ +name: Send notifications + +on: [ release, issues, issue_comment, watch, pull_request_target ] + +jobs: + notification: + uses: maikschneider/reusable-workflows/.github/workflows/notifications.yml@main + secrets: + teams-webhook-url: ${{ secrets.TEAMS_WEBHOOK_URL }} diff --git a/.github/workflows/test-sca.yml b/.github/workflows/test-sca.yml new file mode 100644 index 0000000..af1d23c --- /dev/null +++ b/.github/workflows/test-sca.yml @@ -0,0 +1,12 @@ +name: Static Code Analysis + +on: + - pull_request + - push + +jobs: + + tests: + uses: maikschneider/reusable-workflows/.github/workflows/sca.yml@main + with: + php-version: 8.1 From d65650f3c4dd90b624592cc9e7aee7f2acef3249 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Sat, 2 Mar 2024 19:13:51 +0100 Subject: [PATCH 38/66] feat: init UpgradeWizard --- Classes/Upgrades/ElementUpgradeWizard.php | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Classes/Upgrades/ElementUpgradeWizard.php diff --git a/Classes/Upgrades/ElementUpgradeWizard.php b/Classes/Upgrades/ElementUpgradeWizard.php new file mode 100644 index 0000000..9a5ab74 --- /dev/null +++ b/Classes/Upgrades/ElementUpgradeWizard.php @@ -0,0 +1,52 @@ +getQueryBuilderForTable('tt_content'); + $elements = $qb->select('*') + ->from('tt_content') + ->where($qb->expr()->and( + $qb->expr()->eq('CType', $qb->createNamedParameter('list')), + $qb->expr()->eq('list_type', $qb->createNamedParameter('Xmformcycle')) + )) + ->executeQuery() + ->fetchAllAssociative(); + + return (bool)count($elements); + } + + public function getPrerequisites(): array + { + return ['database up-to-date']; + } +} From daa163c19ef0f662efc9b58c99a29ff3b5b96f18 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Sun, 3 Mar 2024 20:15:30 +0100 Subject: [PATCH 39/66] feat: configure test coverage output --- .gitignore | 1 + phpunit.unit.xml | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index 903abc3..4847cac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.Build .idea public/.htaccess public/_assets diff --git a/phpunit.unit.xml b/phpunit.unit.xml index 121958e..804c485 100644 --- a/phpunit.unit.xml +++ b/phpunit.unit.xml @@ -5,11 +5,21 @@ colors="true" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" > + + + + + + + Tests/Unit + + + Classes From db582c722ffa900fc5fe3ee3af73218dfa8d2f8f Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Mon, 4 Mar 2024 08:33:26 +0100 Subject: [PATCH 40/66] feat: update readme and descriptions, add license --- Configuration/TCA/Overrides/sys_template.php | 2 +- LICENSE.md | 294 +++++++++++++++++++ README.md | 129 +++++--- Resources/Public/Icons/Extension.svg | 2 +- composer.json | 6 +- ext_conf_template.txt | 13 +- ext_emconf.php | 2 +- 7 files changed, 391 insertions(+), 57 deletions(-) create mode 100644 LICENSE.md diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index ede8f41..b7de2d7 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -7,5 +7,5 @@ ExtensionManagementUtility::addStaticFile( 'xm_formcycle', 'Configuration/TypoScript', - 'FORMCYCLE' + 'Formcycle' ); diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5b353ba --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,294 @@ +## GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +### Preamble + +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, +we want its recipients to know that what they have is not the +original, so that any problems introduced by others will not reflect +on the original authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at +all. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work +based on the Program" means either the Program or any derivative work +under copyright law: that is to say, a work containing the Program or +a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is +included without limitation in the term "modification".) Each licensee +is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a +fee. + +**2.** You may modify your copy or copies of the Program or any +portion of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + +**a)** You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + + +**b)** You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any part +thereof, to be licensed as a whole at no charge to all third parties +under the terms of this License. + + +**c)** If the modified program normally reads commands interactively +when run, you must cause it, when started running for such interactive +use in the most ordinary way, to print or display an announcement +including an appropriate copyright notice and a notice that there is +no warranty (or else, saying that you provide a warranty) and that +users may redistribute the program under these conditions, and telling +the user how to view a copy of this License. (Exception: if the +Program itself is interactive but does not normally print such an +announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + +**a)** Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections 1 +and 2 above on a medium customarily used for software interchange; or, + + +**b)** Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your cost of +physically performing source distribution, a complete machine-readable +copy of the corresponding source code, to be distributed under the +terms of Sections 1 and 2 above on a medium customarily used for +software interchange; or, + + +**c)** Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is allowed +only for noncommercial distribution and only if you received the +program in object code or executable form with such an offer, in +accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and +will automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on +the Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +**7.** If, as a consequence of a court judgment or allegation of +patent infringement or for any other reason (not limited to patent +issues), conditions are imposed on you (whether by court order, +agreement or otherwise) that contradict the conditions of this +License, they do not excuse you from the conditions of this License. +If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, +then as a consequence you may not distribute the Program at all. For +example, if a patent license would not permit royalty-free +redistribution of the Program by all those who receive copies directly +or indirectly through you, then the only way you could satisfy both it +and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +**9.** The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + +**10.** If you wish to incorporate parts of the Program into other +free programs whose distribution conditions are different, write to +the author to ask for permission. For software which is copyrighted by +the Free Software Foundation, write to the Free Software Foundation; +we sometimes make exceptions for this. Our decision will be guided by +the two goals of preserving the free status of all derivatives of our +free software and of promoting the sharing and reuse of software +generally. + +**NO WARRANTY** + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +### END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md index 8d6921d..3de842d 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,83 @@ -Formcycle -========= - -Content -------- -- [What does it do?](#What-does-it-do) -- [Author](#Author) -- [Dependencies](#Dependencies) -- [Installation / Configuration](#Installation) --- [TypoScript Setup](#TypoScript-Setup) --- [TypoScript Constants](#TypoScript-Constants) -- [Plug-Ins](#Plug-ins) -- [Help](#Help) - -## What does it do? -Connects to your form created with XIMA® FormCycle (form management with professional form designer, process management, inbox and more features). - -## Author -XIMA MEDIA GmbH ([Website](https://www.xima.de/)) - -## Dependencies -- TYPO3-Extensions: see [ext_emconf.php](tree/Source/xm_slider/ext_emconf.php) (section "constraints") - -## Installation / Configuration -1. Installation via Extension Manager or by copying into typo3conf/ext/xm_formcycle. -2. Set Extension configuration via Extension Manager -2. Include static templates -3. Configure (Constants-Editor, Template-Settings, Plug-In-Settings) - -> -> For further information see our [wiki page](https://help.formcycle.eu/xwiki/bin/view/Formcycle/CMSExtension/Typo3Extension/). -> - -### TypoScript Setup -- **tx_xmformcycle_ajax** - AJAX configuration with typeNum 1464705954. -### TypoScript Constants -- **plugin.tx_xmformcycle.settings.enableJs** - Including JavaScript is the default. But you can disable it (=0) for your own asset management. - -## Plug-Ins -- FormCycle Integrator: Default Plug-In to show your form - -## Help -Do you found an error, need help or have any suggestions for us? -Please send us an e-mail to: support@formcycle.de +
+ +![Extension icon](Resources/Public/Icons/Extension.svg) + +# TYPO3 extension `xm_formcycle` + +[![Supported TYPO3 versions](https://typo3-badges.dev/badge/xm_formcycle/typo3/shields.svg)](https://extensions.typo3.org/extension/xm_formcycle) + +
+ +A TYPO3 extension that connects to [XIMA® Formcycle](https://www.formcycle.eu/). Select your created forms and embed +them into your TYPO3 site. + +## Requirements + +* The Formcycle plugin `FormList` musst be installed +* PHP 8.1+ + +## Installation + +### Composer + +```bash +composer require xima/xima-typo3-formcycle +``` + +### TER + +[![TER version](https://typo3-badges.dev/badge/xm_formcycle/version/shields.svg)](https://extensions.typo3.org/extension/xm_formcycle) + +Download the zip file from +[TYPO3 extension repository (TER)](https://extensions.typo3.org/extension/xm_formcycle). + +## Configuration + +After installation, enter your login data via extension configuration and include the TypoScript template for the +frontend rendering. + +### 1. Extension configuration + +Set your Formcycle credentials in the extension configuration via TYPO3 backend or in your `config/system/settings.php`: + +```php +'EXTENSIONS' => [ + 'xm_formcycle' => [ + 'formCycleUrl' => 'https://pro.formcloud.de/', + 'formCycleClientId' => '4231', + 'formCycleUser' => 'user@examle.com', + 'formCyclePass' => 'the-password', + 'formCycleFrontendUrl' => '', // optional + 'integrationMode' => '', // optional + ], +] +``` + +### 2. TypoScript include + +Include the static TypoScript template "Formcycle" or directly import it in your sitepackage: + +```typo3_typoscript +@import 'EXT:xm_formcycle/Configuration/TypoScript/setup.typoscript' +``` + +## Developer + +If you want to modify the [fluid template](Resources/Private/Templates/Formcycle.html), add template paths via +TypoScript constants: + +```typo3_typoscript +plugin.tx_xmformcycle { + view { + templateRootPath = EXT:your_ext/Resources/Private/Templates + partialRootPath = EXT:your_ext/Resources/Private/Partials + layoutRootPath = EXT:your_ext/Resources/Private/Layouts + } +} +``` + +Copy and modify the `Formcycle.html` to the Templates directory. + +## License + +This project is licensed under [GNU General Public License 2.0 (or later)](LICENSE.md). diff --git a/Resources/Public/Icons/Extension.svg b/Resources/Public/Icons/Extension.svg index 49f9f00..5fcec4c 100644 --- a/Resources/Public/Icons/Extension.svg +++ b/Resources/Public/Icons/Extension.svg @@ -1 +1 @@ - + diff --git a/composer.json b/composer.json index b179960..3aa804f 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { - "name": "xima-media/xm_formcycle", + "name": "xima/xima-typo3-formcycle", "type": "typo3-cms-extension", - "description": "Integrator für FORMCYCLE", + "description": "TYPO3 extension to include your XIMA Formcycle forms", "homepage": "https://www.xima.de", "license": [ "GPL-2.0+" @@ -15,7 +15,7 @@ { "name": "Maik Schneider", "role": "Developer", - "email": "schneider.maik@me.com" + "email": "maik.schneider@xima.de" } ], "require": { diff --git a/ext_conf_template.txt b/ext_conf_template.txt index d335f00..05de4c8 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -1,14 +1,17 @@ -# cat=basic//100; type=string; label=XIMA FORMCYCLE Server URL (required) :e.g. https://pro.formcloud.de/formcycle +# cat=basic//100; type=string; label=Formcycle server URL, e.g. https://pro.formcloud.de/ formCycleUrl = -# cat=basic//100; type=string; label=XIMA FORMCYCLE Frontend Server URL (optional) :e.g. https://pro.formcloud.de/formcycle +# cat=basic//100; type=string; label=The client ID of your Formcycle installation +formCycleClientId = + +# cat=basic//100; type=string; label=Formcycle Frontend Server URL (optional) formCycleFrontendUrl = -# cat=basic//102; type=string; label=XIMA FORMCYCLE login name (optional) :Used for prefilling the login screen within the backend, e.g. max@mandant.net +# cat=basic//102; type=string; label=Formcycle login username formCycleUser = -# cat=basic//103; type=string; label=XIMA FORMCYCLE password (optional) :Used for prefilling the login screen within the backend. +# cat=basic//103; type=string; label=Formcycle login password formCyclePass = -# cat=basic//104; type=options[integrated, AJAX (TYPO3), AJAX (FORMCYCLE), iFrame]; label= Integration mode +# cat=basic//104; type=options[integrated, AJAX (TYPO3), AJAX (FORMCYCLE), iFrame]; label=Integration mode integrationMode = integrated diff --git a/ext_emconf.php b/ext_emconf.php index 289d1ed..fc87de1 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -2,7 +2,7 @@ $EM_CONF[$_EXTKEY] = [ 'title' => 'formcylce', - 'description' => 'Form management with professional form designer, process management, inbox and more.', + 'description' => 'Embed XIMA Formcycle forms into your TYPO3 website', 'category' => 'plugin', 'version' => '9.0.0', 'state' => 'stable', From 64afcfdefaf1f4eb96aa5c815c7417916273680b Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Mon, 4 Mar 2024 09:09:12 +0100 Subject: [PATCH 41/66] feat: make client id configurable via ext configuration --- Classes/Dto/FormcycleConfiguration.php | 17 +++++++++++++---- Classes/Upgrades/ElementUpgradeWizard.php | 1 - Configuration/FlexForms/flexform_list.xml | 3 +-- Resources/Private/Language/de.locallang.xlf | 12 ++++++++++-- Resources/Private/Language/locallang.xlf | 8 +++++++- .../Templates/Backend/FormcycleSelection.html | 4 ++-- .../Backend/FormcycleSelectionElement.js | 7 +++++++ Tests/Unit/Dto/FormcycleConfigurationTest.php | 18 ++++++++++++++++-- 8 files changed, 56 insertions(+), 14 deletions(-) diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php index 323a92e..656e973 100644 --- a/Classes/Dto/FormcycleConfiguration.php +++ b/Classes/Dto/FormcycleConfiguration.php @@ -17,7 +17,7 @@ final class FormcycleConfiguration private IntegrationMode $integrationMode; - private string $client = '24871'; + private string $formCycleClientId; /** * @throws FormcycleConfigurationException @@ -34,9 +34,18 @@ public static function createFromExtensionConfiguration(array $extConfiguration) } $config->formCycleUser = $extConfiguration['formCycleUser'] ?? ''; + if (!$config->formCycleUser) { + throw new FormcycleConfigurationException('No formCycleUser set', 1709052037); + } + $config->formCyclePass = $extConfiguration['formCyclePass'] ?? ''; - if (!$config->formCycleUser || !$config->formCyclePass) { - throw new FormcycleConfigurationException('No formCycleUser or formCyclePass set', 1709052037); + if (!$config->formCyclePass) { + throw new FormcycleConfigurationException('No formCyclePass set', 1709538727); + } + + $config->formCycleClientId = $extConfiguration['formCycleClientId'] ?? ''; + if (!$config->formCycleClientId) { + throw new FormcycleConfigurationException('No formCycleClientId set', 1709538688); } $config->integrationMode = IntegrationMode::tryFrom($extConfiguration['integrationMode'] ?? '') ?? IntegrationMode::Integrated; @@ -59,7 +68,7 @@ public function getFormListUrl(): string $this->formCycleUrl, $this->formCycleUser, $this->formCyclePass, - $this->client, + $this->formCycleClientId, ); } diff --git a/Classes/Upgrades/ElementUpgradeWizard.php b/Classes/Upgrades/ElementUpgradeWizard.php index 9a5ab74..8ff3f1a 100644 --- a/Classes/Upgrades/ElementUpgradeWizard.php +++ b/Classes/Upgrades/ElementUpgradeWizard.php @@ -11,7 +11,6 @@ #[UpgradeWizard('xmFormcycle_migrateElements')] class ElementUpgradeWizard implements UpgradeWizardInterface { - public function getTitle(): string { return 'Formcycle element upgrade wizard'; diff --git a/Configuration/FlexForms/flexform_list.xml b/Configuration/FlexForms/flexform_list.xml index 0a63647..29ce64b 100644 --- a/Configuration/FlexForms/flexform_list.xml +++ b/Configuration/FlexForms/flexform_list.xml @@ -22,8 +22,7 @@ pages 1 1 - none - 0 + 0 diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf index 995fcb7..ab6a46c 100644 --- a/Resources/Private/Language/de.locallang.xlf +++ b/Resources/Private/Language/de.locallang.xlf @@ -39,8 +39,16 @@ Die Extension Konfiguration "formCycleUrl" fehlt oder ist ungültig - The extension configuration "formCycleUser" or "formCyclePass" is missing - Die Extension Konfiguration "formCycleUser" oder "formCyclePass" fehlt + The extension configuration "formCycleUser" is missing + Die Extension Konfiguration "formCycleUser" fehlt + + + The extension configuration "formCyclePass" is missing + Die Extension Konfiguration "formCyclePass" fehlt + + + The extension configuration "formCycleClientId" is missing + Die Extension Konfiguration "formCycleClientId" fehlt The extension configuration "integrationMode" is invalid. Valid options are: "integrated", "iFrame" or "AJAX". diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 2b25710..0059913 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -33,7 +33,13 @@ The extension configuration "formCycleUrl" is missing or invalid - The extension configuration "formCycleUser" or "formCyclePass" is missing + The extension configuration "formCycleUser" is missing + + + The extension configuration "formCyclePass" is missing + + + The extension configuration "formCycleClientId" is missing The extension configuration "integrationMode" is invalid. Valid options are: "integrated", "iFrame" or "AJAX". diff --git a/Resources/Private/Templates/Backend/FormcycleSelection.html b/Resources/Private/Templates/Backend/FormcycleSelection.html index c9ab8a2..7f7260b 100644 --- a/Resources/Private/Templates/Backend/FormcycleSelection.html +++ b/Resources/Private/Templates/Backend/FormcycleSelection.html @@ -4,7 +4,7 @@ - +
- +
diff --git a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js index 39833ef..0acbbb4 100644 --- a/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js +++ b/Resources/Public/JavaScript/Backend/FormcycleSelectionElement.js @@ -6,6 +6,13 @@ export default class FormcycleSelectionElement { constructor(itemFormElID = '') { this.hiddenInputElement = document.querySelector('#' + itemFormElID) + + const hasError = document.querySelector('#xm-formcycle-forms .callout-danger') + if (hasError) { + console.debug('Formcycle configuration has errors, aborting') + return + } + this.init() } diff --git a/Tests/Unit/Dto/FormcycleConfigurationTest.php b/Tests/Unit/Dto/FormcycleConfigurationTest.php index f44593a..75eea94 100644 --- a/Tests/Unit/Dto/FormcycleConfigurationTest.php +++ b/Tests/Unit/Dto/FormcycleConfigurationTest.php @@ -14,15 +14,21 @@ class FormcycleConfigurationTest extends UnitTestCase 'formCycleFrontendUrl' => '', 'formCycleUser' => 'username', 'formCyclePass' => 'password', + 'formCycleClientId' => '123456', 'integrationMode' => '', ]; - public function testCreateFromExtensionConfiguration(): void + public function testCreateFromEmptyExtensionConfiguration(): void { $this->expectException(FormcycleConfigurationException::class); FormcycleConfiguration::createFromExtensionConfiguration([]); } + public function testCreateValidExtensionConfiguration(): void + { + FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + } + public function testInvalidFormcycleUrl(): void { $this->expectException(FormcycleConfigurationException::class); @@ -50,11 +56,19 @@ public function testMissingUsername(): void public function testMissingPassword(): void { $this->expectException(FormcycleConfigurationException::class); - $this->expectExceptionCode(1709052037); + $this->expectExceptionCode(1709538727); $this->validExtensionConfiguration['formCyclePass'] = ''; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } + public function testMissingClientId(): void + { + $this->expectException(FormcycleConfigurationException::class); + $this->expectExceptionCode(1709538688); + $this->validExtensionConfiguration['formCycleClientId'] = ''; + FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + } + public function testDefaultIntegrationMode(): void { $config = FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); From 1ff52444ae31947590e39e95f712d6c84db3637d Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Mon, 4 Mar 2024 09:23:43 +0100 Subject: [PATCH 42/66] feat: install jquery & jquery-ui via npm --- Classes/DataProcessing/AjaxProcessor.php | 5 +++- Classes/Dto/ElementSettings.php | 2 +- .../JavaScript/Frontend/jquery-ui.min.js | 6 ++++ .../{jquery-3.7.1.min.js => jquery.min.js} | 0 package-lock.json | 30 +++++++++++++++++++ package.json | 19 ++++++++++++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 Resources/Public/JavaScript/Frontend/jquery-ui.min.js rename Resources/Public/JavaScript/Frontend/{jquery-3.7.1.min.js => jquery.min.js} (100%) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/Classes/DataProcessing/AjaxProcessor.php b/Classes/DataProcessing/AjaxProcessor.php index 1f4abf3..fad7672 100644 --- a/Classes/DataProcessing/AjaxProcessor.php +++ b/Classes/DataProcessing/AjaxProcessor.php @@ -18,7 +18,10 @@ public function subProcess( /** @var PageRenderer $pageRenderer */ $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); if ($this->settings->loadFormcycleJquery) { - $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js'); + $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/jquery.min.js'); + } + if ($this->settings->loadFormcycleJqueryUi) { + $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/jquery-ui.min.js'); } $pageRenderer->addJsFooterFile('EXT:xm_formcycle/Resources/Public/JavaScript/Frontend/FormcycleAjax.js'); diff --git a/Classes/Dto/ElementSettings.php b/Classes/Dto/ElementSettings.php index 722d71f..371a7f8 100644 --- a/Classes/Dto/ElementSettings.php +++ b/Classes/Dto/ElementSettings.php @@ -17,7 +17,7 @@ class ElementSettings public bool $loadFormcycleJquery = true; - public bool $loadFormcycleJqueryUi = true; + public bool $loadFormcycleJqueryUi = false; public bool $loadResponseJs = false; diff --git a/Resources/Public/JavaScript/Frontend/jquery-ui.min.js b/Resources/Public/JavaScript/Frontend/jquery-ui.min.js new file mode 100644 index 0000000..50b036f --- /dev/null +++ b/Resources/Public/JavaScript/Frontend/jquery-ui.min.js @@ -0,0 +1,6 @@ +/*! jQuery UI - v1.13.2 - 2022-07-14 +* http://jqueryui.com +* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +!function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(V){"use strict";V.ui=V.ui||{};V.ui.version="1.13.2";var n,i=0,a=Array.prototype.hasOwnProperty,r=Array.prototype.slice;V.cleanData=(n=V.cleanData,function(t){for(var e,i,s=0;null!=(i=t[s]);s++)(e=V._data(i,"events"))&&e.remove&&V(i).triggerHandler("remove");n(t)}),V.widget=function(t,i,e){var s,n,o,a={},r=t.split(".")[0],l=r+"-"+(t=t.split(".")[1]);return e||(e=i,i=V.Widget),Array.isArray(e)&&(e=V.extend.apply(null,[{}].concat(e))),V.expr.pseudos[l.toLowerCase()]=function(t){return!!V.data(t,l)},V[r]=V[r]||{},s=V[r][t],n=V[r][t]=function(t,e){if(!this||!this._createWidget)return new n(t,e);arguments.length&&this._createWidget(t,e)},V.extend(n,s,{version:e.version,_proto:V.extend({},e),_childConstructors:[]}),(o=new i).options=V.widget.extend({},o.options),V.each(e,function(e,s){function n(){return i.prototype[e].apply(this,arguments)}function o(t){return i.prototype[e].apply(this,t)}a[e]="function"==typeof s?function(){var t,e=this._super,i=this._superApply;return this._super=n,this._superApply=o,t=s.apply(this,arguments),this._super=e,this._superApply=i,t}:s}),n.prototype=V.widget.extend(o,{widgetEventPrefix:s&&o.widgetEventPrefix||t},a,{constructor:n,namespace:r,widgetName:t,widgetFullName:l}),s?(V.each(s._childConstructors,function(t,e){var i=e.prototype;V.widget(i.namespace+"."+i.widgetName,n,e._proto)}),delete s._childConstructors):i._childConstructors.push(n),V.widget.bridge(t,n),n},V.widget.extend=function(t){for(var e,i,s=r.call(arguments,1),n=0,o=s.length;n",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=V(e||this.defaultElement||this)[0],this.element=V(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=V(),this.hoverable=V(),this.focusable=V(),this.classesElementLookup={},e!==this&&(V.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=V(e.style?e.ownerDocument:e.document||e),this.window=V(this.document[0].defaultView||this.document[0].parentWindow)),this.options=V.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:V.noop,_create:V.noop,_init:V.noop,destroy:function(){var i=this;this._destroy(),V.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:V.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return V.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=V.widget.extend({},this.options[t]),n=0;n
"),i=e.children()[0];return V("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i},getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.widthx(k(s),k(n))?o.important="horizontal":o.important="vertical",u.using.call(this,t,o)}),a.offset(V.extend(h,{using:t}))})},V.ui.position={fit:{left:function(t,e){var i=e.within,s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,a=s-o,r=o+e.collisionWidth-n-s;e.collisionWidth>n?0n?0")[0],w=d.each;function P(t){return null==t?t+"":"object"==typeof t?p[e.call(t)]||"object":typeof t}function M(t,e,i){var s=v[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:Math.min(s.max,Math.max(0,t)))}function S(s){var n=m(),o=n._rgba=[];return s=s.toLowerCase(),w(g,function(t,e){var i=e.re.exec(s),i=i&&e.parse(i),e=e.space||"rgba";if(i)return i=n[e](i),n[_[e].cache]=i[_[e].cache],o=n._rgba=i._rgba,!1}),o.length?("0,0,0,0"===o.join()&&d.extend(o,B.transparent),n):B[s]}function H(t,e,i){return 6*(i=(i+1)%1)<1?t+(e-t)*i*6:2*i<1?e:3*i<2?t+(e-t)*(2/3-i)*6:t}y.style.cssText="background-color:rgba(1,1,1,.5)",b.rgba=-1o.mod/2?s+=o.mod:s-n>o.mod/2&&(s-=o.mod)),l[i]=M((n-s)*a+s,e)))}),this[e](l)},blend:function(t){if(1===this._rgba[3])return this;var e=this._rgba.slice(),i=e.pop(),s=m(t)._rgba;return m(d.map(e,function(t,e){return(1-i)*s[e]+i*t}))},toRgbaString:function(){var t="rgba(",e=d.map(this._rgba,function(t,e){return null!=t?t:2
").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e={width:i.width(),height:i.height()},n=document.activeElement;try{n.id}catch(t){n=document.body}return i.wrap(t),i[0]!==n&&!V.contains(i[0],n)||V(n).trigger("focus"),t=i.parent(),"static"===i.css("position")?(t.css({position:"relative"}),i.css({position:"relative"})):(V.extend(s,{position:i.css("position"),zIndex:i.css("z-index")}),V.each(["top","left","bottom","right"],function(t,e){s[e]=i.css(e),isNaN(parseInt(s[e],10))&&(s[e]="auto")}),i.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),i.css(e),t.css(s).show()},removeWrapper:function(t){var e=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),t[0]!==e&&!V.contains(t[0],e)||V(e).trigger("focus")),t}}),V.extend(V.effects,{version:"1.13.2",define:function(t,e,i){return i||(i=e,e="effect"),V.effects.effect[t]=i,V.effects.effect[t].mode=e,i},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,e="vertical"!==i?(e||100)/100:1;return{height:t.height()*e,width:t.width()*s,outerHeight:t.outerHeight()*e,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();1").insertAfter(t).css({display:/^(inline|ruby)/.test(t.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:t.css("marginTop"),marginBottom:t.css("marginBottom"),marginLeft:t.css("marginLeft"),marginRight:t.css("marginRight"),float:t.css("float")}).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).addClass("ui-effects-placeholder"),t.data(j+"placeholder",e)),t.css({position:i,left:s.left,top:s.top}),e},removePlaceholder:function(t){var e=j+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(t){V.effects.restoreStyle(t),V.effects.removePlaceholder(t)},setTransition:function(s,t,n,o){return o=o||{},V.each(t,function(t,e){var i=s.cssUnit(e);0

");l.appendTo("body").addClass(t.className).css({top:s.top-a,left:s.left-r,height:i.innerHeight(),width:i.innerWidth(),position:n?"fixed":"absolute"}).animate(o,t.duration,t.easing,function(){l.remove(),"function"==typeof e&&e()})}}),V.fx.step.clip=function(t){t.clipInit||(t.start=V(t.elem).cssClip(),"string"==typeof t.end&&(t.end=G(t.end,t.elem)),t.clipInit=!0),V(t.elem).cssClip({top:t.pos*(t.end.top-t.start.top)+t.start.top,right:t.pos*(t.end.right-t.start.right)+t.start.right,bottom:t.pos*(t.end.bottom-t.start.bottom)+t.start.bottom,left:t.pos*(t.end.left-t.start.left)+t.start.left})},Y={},V.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,t){Y[t]=function(t){return Math.pow(t,e+2)}}),V.extend(Y,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;t<((e=Math.pow(2,--i))-1)/11;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),V.each(Y,function(t,e){V.easing["easeIn"+t]=e,V.easing["easeOut"+t]=function(t){return 1-e(1-t)},V.easing["easeInOut"+t]=function(t){return t<.5?e(2*t)/2:1-e(-2*t+2)/2}});y=V.effects,V.effects.define("blind","hide",function(t,e){var i={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},s=V(this),n=t.direction||"up",o=s.cssClip(),a={clip:V.extend({},o)},r=V.effects.createPlaceholder(s);a.clip[i[n][0]]=a.clip[i[n][1]],"show"===t.mode&&(s.cssClip(a.clip),r&&r.css(V.effects.clipToBox(a)),a.clip=o),r&&r.animate(V.effects.clipToBox(a),t.duration,t.easing),s.animate(a,{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("bounce",function(t,e){var i,s,n=V(this),o=t.mode,a="hide"===o,r="show"===o,l=t.direction||"up",h=t.distance,c=t.times||5,o=2*c+(r||a?1:0),u=t.duration/o,d=t.easing,p="up"===l||"down"===l?"top":"left",f="up"===l||"left"===l,g=0,t=n.queue().length;for(V.effects.createPlaceholder(n),l=n.css(p),h=h||n["top"==p?"outerHeight":"outerWidth"]()/3,r&&((s={opacity:1})[p]=l,n.css("opacity",0).css(p,f?2*-h:2*h).animate(s,u,d)),a&&(h/=Math.pow(2,c-1)),(s={})[p]=l;g

").css({position:"absolute",visibility:"visible",left:-s*p,top:-i*f}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:p,height:f,left:n+(u?a*p:0),top:o+(u?r*f:0),opacity:u?0:1}).animate({left:n+(u?0:a*p),top:o+(u?0:r*f),opacity:u?1:0},t.duration||500,t.easing,m)}),V.effects.define("fade","toggle",function(t,e){var i="show"===t.mode;V(this).css("opacity",i?0:1).animate({opacity:i?1:0},{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("fold","hide",function(e,t){var i=V(this),s=e.mode,n="show"===s,o="hide"===s,a=e.size||15,r=/([0-9]+)%/.exec(a),l=!!e.horizFirst?["right","bottom"]:["bottom","right"],h=e.duration/2,c=V.effects.createPlaceholder(i),u=i.cssClip(),d={clip:V.extend({},u)},p={clip:V.extend({},u)},f=[u[l[0]],u[l[1]]],s=i.queue().length;r&&(a=parseInt(r[1],10)/100*f[o?0:1]),d.clip[l[0]]=a,p.clip[l[0]]=a,p.clip[l[1]]=0,n&&(i.cssClip(p.clip),c&&c.css(V.effects.clipToBox(p)),p.clip=u),i.queue(function(t){c&&c.animate(V.effects.clipToBox(d),h,e.easing).animate(V.effects.clipToBox(p),h,e.easing),t()}).animate(d,h,e.easing).animate(p,h,e.easing).queue(t),V.effects.unshift(i,s,4)}),V.effects.define("highlight","show",function(t,e){var i=V(this),s={backgroundColor:i.css("backgroundColor")};"hide"===t.mode&&(s.opacity=0),V.effects.saveStyle(i),i.css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(s,{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("size",function(s,e){var n,i=V(this),t=["fontSize"],o=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],a=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],r=s.mode,l="effect"!==r,h=s.scale||"both",c=s.origin||["middle","center"],u=i.css("position"),d=i.position(),p=V.effects.scaledDimensions(i),f=s.from||p,g=s.to||V.effects.scaledDimensions(i,0);V.effects.createPlaceholder(i),"show"===r&&(r=f,f=g,g=r),n={from:{y:f.height/p.height,x:f.width/p.width},to:{y:g.height/p.height,x:g.width/p.width}},"box"!==h&&"both"!==h||(n.from.y!==n.to.y&&(f=V.effects.setTransition(i,o,n.from.y,f),g=V.effects.setTransition(i,o,n.to.y,g)),n.from.x!==n.to.x&&(f=V.effects.setTransition(i,a,n.from.x,f),g=V.effects.setTransition(i,a,n.to.x,g))),"content"!==h&&"both"!==h||n.from.y!==n.to.y&&(f=V.effects.setTransition(i,t,n.from.y,f),g=V.effects.setTransition(i,t,n.to.y,g)),c&&(c=V.effects.getBaseline(c,p),f.top=(p.outerHeight-f.outerHeight)*c.y+d.top,f.left=(p.outerWidth-f.outerWidth)*c.x+d.left,g.top=(p.outerHeight-g.outerHeight)*c.y+d.top,g.left=(p.outerWidth-g.outerWidth)*c.x+d.left),delete f.outerHeight,delete f.outerWidth,i.css(f),"content"!==h&&"both"!==h||(o=o.concat(["marginTop","marginBottom"]).concat(t),a=a.concat(["marginLeft","marginRight"]),i.find("*[width]").each(function(){var t=V(this),e=V.effects.scaledDimensions(t),i={height:e.height*n.from.y,width:e.width*n.from.x,outerHeight:e.outerHeight*n.from.y,outerWidth:e.outerWidth*n.from.x},e={height:e.height*n.to.y,width:e.width*n.to.x,outerHeight:e.height*n.to.y,outerWidth:e.width*n.to.x};n.from.y!==n.to.y&&(i=V.effects.setTransition(t,o,n.from.y,i),e=V.effects.setTransition(t,o,n.to.y,e)),n.from.x!==n.to.x&&(i=V.effects.setTransition(t,a,n.from.x,i),e=V.effects.setTransition(t,a,n.to.x,e)),l&&V.effects.saveStyle(t),t.css(i),t.animate(e,s.duration,s.easing,function(){l&&V.effects.restoreStyle(t)})})),i.animate(g,{queue:!1,duration:s.duration,easing:s.easing,complete:function(){var t=i.offset();0===g.opacity&&i.css("opacity",f.opacity),l||(i.css("position","static"===u?"relative":u).offset(t),V.effects.saveStyle(i)),e()}})}),V.effects.define("scale",function(t,e){var i=V(this),s=t.mode,s=parseInt(t.percent,10)||(0===parseInt(t.percent,10)||"effect"!==s?0:100),s=V.extend(!0,{from:V.effects.scaledDimensions(i),to:V.effects.scaledDimensions(i,s,t.direction||"both"),origin:t.origin||["middle","center"]},t);t.fade&&(s.from.opacity=1,s.to.opacity=0),V.effects.effect.size.call(this,s,e)}),V.effects.define("puff","hide",function(t,e){t=V.extend(!0,{},t,{fade:!0,percent:parseInt(t.percent,10)||150});V.effects.effect.scale.call(this,t,e)}),V.effects.define("pulsate","show",function(t,e){var i=V(this),s=t.mode,n="show"===s,o=2*(t.times||5)+(n||"hide"===s?1:0),a=t.duration/o,r=0,l=1,s=i.queue().length;for(!n&&i.is(":visible")||(i.css("opacity",0).show(),r=1);l li > :first-child").add(t.find("> :not(li)").even())},heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var t=this.options;this.prevShow=this.prevHide=V(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),t.collapsible||!1!==t.active&&null!=t.active||(t.active=0),this._processPanels(),t.active<0&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():V()}},_createIcons:function(){var t,e=this.options.icons;e&&(t=V(""),this._addClass(t,"ui-accordion-header-icon","ui-icon "+e.header),t.prependTo(this.headers),t=this.active.children(".ui-accordion-header-icon"),this._removeClass(t,e.header)._addClass(t,null,e.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){"active"!==t?("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||!1!==this.options.active||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons())):this._activate(e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var e=V.ui.keyCode,i=this.headers.length,s=this.headers.index(t.target),n=!1;switch(t.keyCode){case e.RIGHT:case e.DOWN:n=this.headers[(s+1)%i];break;case e.LEFT:case e.UP:n=this.headers[(s-1+i)%i];break;case e.SPACE:case e.ENTER:this._eventHandler(t);break;case e.HOME:n=this.headers[0];break;case e.END:n=this.headers[i-1]}n&&(V(t.target).attr("tabIndex",-1),V(n).attr("tabIndex",0),V(n).trigger("focus"),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===V.ui.keyCode.UP&&t.ctrlKey&&V(t.currentTarget).prev().trigger("focus")},refresh:function(){var t=this.options;this._processPanels(),!1===t.active&&!0===t.collapsible||!this.headers.length?(t.active=!1,this.active=V()):!1===t.active?this._activate(0):this.active.length&&!V.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=V()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;"function"==typeof this.options.header?this.headers=this.options.header(this.element):this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var i,t=this.options,e=t.heightStyle,s=this.element.parent();this.active=this._findActive(t.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var t=V(this),e=t.uniqueId().attr("id"),i=t.next(),s=i.uniqueId().attr("id");t.attr("aria-controls",s),i.attr("aria-labelledby",e)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(t.event),"fill"===e?(i=s.height(),this.element.siblings(":visible").each(function(){var t=V(this),e=t.css("position");"absolute"!==e&&"fixed"!==e&&(i-=t.outerHeight(!0))}),this.headers.each(function(){i-=V(this).outerHeight(!0)}),this.headers.next().each(function(){V(this).height(Math.max(0,i-V(this).innerHeight()+V(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.headers.next().each(function(){var t=V(this).is(":visible");t||V(this).show(),i=Math.max(i,V(this).css("height","").height()),t||V(this).hide()}).height(i))},_activate:function(t){t=this._findActive(t)[0];t!==this.active[0]&&(t=t||this.active[0],this._eventHandler({target:t,currentTarget:t,preventDefault:V.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):V()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&V.each(t.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var e=this.options,i=this.active,s=V(t.currentTarget),n=s[0]===i[0],o=n&&e.collapsible,a=o?V():s.next(),r=i.next(),a={oldHeader:i,oldPanel:r,newHeader:o?V():s,newPanel:a};t.preventDefault(),n&&!e.collapsible||!1===this._trigger("beforeActivate",t,a)||(e.active=!o&&this.headers.index(s),this.active=n?V():s,this._toggle(a),this._removeClass(i,"ui-accordion-header-active","ui-state-active"),e.icons&&(i=i.children(".ui-accordion-header-icon"),this._removeClass(i,null,e.icons.activeHeader)._addClass(i,null,e.icons.header)),n||(this._removeClass(s,"ui-accordion-header-collapsed")._addClass(s,"ui-accordion-header-active","ui-state-active"),e.icons&&(n=s.children(".ui-accordion-header-icon"),this._removeClass(n,null,e.icons.header)._addClass(n,null,e.icons.activeHeader)),this._addClass(s.next(),"ui-accordion-content-active")))},_toggle:function(t){var e=t.newPanel,i=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=e,this.prevHide=i,this.options.animate?this._animate(e,i,t):(i.hide(),e.show(),this._toggleComplete(t)),i.attr({"aria-hidden":"true"}),i.prev().attr({"aria-selected":"false","aria-expanded":"false"}),e.length&&i.length?i.prev().attr({tabIndex:-1,"aria-expanded":"false"}):e.length&&this.headers.filter(function(){return 0===parseInt(V(this).attr("tabIndex"),10)}).attr("tabIndex",-1),e.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,i,e){var s,n,o,a=this,r=0,l=t.css("box-sizing"),h=t.length&&(!i.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.lastMousePosition={x:null,y:null},this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault(),this._activateItem(t)},"click .ui-menu-item":function(t){var e=V(t.target),i=V(V.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&e.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),e.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&i.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":"_activateItem","mousemove .ui-menu-item":"_activateItem",mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this._menuItems().first();e||this.focus(t,i)},blur:function(t){this._delay(function(){V.contains(this.element[0],V.ui.safeActiveElement(this.document[0]))||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t,!0),this.mouseHandled=!1}})},_activateItem:function(t){var e,i;this.previousFilter||t.clientX===this.lastMousePosition.x&&t.clientY===this.lastMousePosition.y||(this.lastMousePosition={x:t.clientX,y:t.clientY},e=V(t.target).closest(".ui-menu-item"),i=V(t.currentTarget),e[0]===i[0]&&(i.is(".ui-state-active")||(this._removeClass(i.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(t,i))))},_destroy:function(){var t=this.element.find(".ui-menu-item").removeAttr("role aria-disabled").children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),t.children().each(function(){var t=V(this);t.data("ui-menu-submenu-caret")&&t.remove()})},_keydown:function(t){var e,i,s,n=!0;switch(t.keyCode){case V.ui.keyCode.PAGE_UP:this.previousPage(t);break;case V.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case V.ui.keyCode.HOME:this._move("first","first",t);break;case V.ui.keyCode.END:this._move("last","last",t);break;case V.ui.keyCode.UP:this.previous(t);break;case V.ui.keyCode.DOWN:this.next(t);break;case V.ui.keyCode.LEFT:this.collapse(t);break;case V.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case V.ui.keyCode.ENTER:case V.ui.keyCode.SPACE:this._activate(t);break;case V.ui.keyCode.ESCAPE:this.collapse(t);break;default:e=this.previousFilter||"",s=n=!1,i=96<=t.keyCode&&t.keyCode<=105?(t.keyCode-96).toString():String.fromCharCode(t.keyCode),clearTimeout(this.filterTimer),i===e?s=!0:i=e+i,e=this._filterMenuItems(i),(e=s&&-1!==e.index(this.active.next())?this.active.nextAll(".ui-menu-item"):e).length||(i=String.fromCharCode(t.keyCode),e=this._filterMenuItems(i)),e.length?(this.focus(t,e),this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}n&&t.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var t,e,s=this,n=this.options.icons.submenu,i=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),e=i.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=V(this),e=t.prev(),i=V("").data("ui-menu-submenu-caret",!0);s._addClass(i,"ui-menu-icon","ui-icon "+n),e.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",e.attr("id"))}),this._addClass(e,"ui-menu","ui-widget ui-widget-content ui-front"),(t=i.add(this.element).find(this.options.items)).not(".ui-menu-item").each(function(){var t=V(this);s._isDivider(t)&&s._addClass(t,"ui-menu-divider","ui-widget-content")}),i=(e=t.not(".ui-menu-item, .ui-menu-divider")).children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(e,"ui-menu-item")._addClass(i,"ui-menu-item-wrapper"),t.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!V.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){var i;"icons"===t&&(i=this.element.find(".ui-menu-icon"),this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",String(t)),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),i=this.active.children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",i.attr("id")),i=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),(i=e.children(".ui-menu")).length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(t){var e,i,s;this._hasScroll()&&(i=parseFloat(V.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(V.css(this.activeMenu[0],"paddingTop"))||0,e=t.offset().top-this.activeMenu.offset().top-i-s,i=this.activeMenu.scrollTop(),s=this.activeMenu.height(),t=t.outerHeight(),e<0?this.activeMenu.scrollTop(i+e):s",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,liveRegionTimer:null,_create:function(){var i,s,n,t=this.element[0].nodeName.toLowerCase(),e="textarea"===t,t="input"===t;this.isMultiLine=e||!t&&this._isContentEditable(this.element),this.valueMethod=this.element[e||t?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(t){if(this.element.prop("readOnly"))s=n=i=!0;else{s=n=i=!1;var e=V.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:i=!0,this._move("previousPage",t);break;case e.PAGE_DOWN:i=!0,this._move("nextPage",t);break;case e.UP:i=!0,this._keyEvent("previous",t);break;case e.DOWN:i=!0,this._keyEvent("next",t);break;case e.ENTER:this.menu.active&&(i=!0,t.preventDefault(),this.menu.select(t));break;case e.TAB:this.menu.active&&this.menu.select(t);break;case e.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(t),t.preventDefault());break;default:s=!0,this._searchTimeout(t)}}},keypress:function(t){if(i)return i=!1,void(this.isMultiLine&&!this.menu.element.is(":visible")||t.preventDefault());if(!s){var e=V.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:this._move("previousPage",t);break;case e.PAGE_DOWN:this._move("nextPage",t);break;case e.UP:this._keyEvent("previous",t);break;case e.DOWN:this._keyEvent("next",t)}}},input:function(t){if(n)return n=!1,void t.preventDefault();this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){clearTimeout(this.searching),this.close(t),this._change(t)}}),this._initSource(),this.menu=V("
    ").appendTo(this._appendTo()).menu({role:null}).hide().attr({unselectable:"on"}).menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault()},menufocus:function(t,e){var i,s;if(this.isNewMenu&&(this.isNewMenu=!1,t.originalEvent&&/^mouse/.test(t.originalEvent.type)))return this.menu.blur(),void this.document.one("mousemove",function(){V(t.target).trigger(t.originalEvent)});s=e.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",t,{item:s})&&t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(s.value),(i=e.item.attr("aria-label")||s.value)&&String.prototype.trim.call(i).length&&(clearTimeout(this.liveRegionTimer),this.liveRegionTimer=this._delay(function(){this.liveRegion.html(V("
    ").text(i))},100))},menuselect:function(t,e){var i=e.item.data("ui-autocomplete-item"),s=this.previous;this.element[0]!==V.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=s,this._delay(function(){this.previous=s,this.selectedItem=i})),!1!==this._trigger("select",t,{item:i})&&this._value(i.value),this.term=this._value(),this.close(t),this.selectedItem=i}}),this.liveRegion=V("
    ",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(t){var e=this.menu.element[0];return t.target===this.element[0]||t.target===e||V.contains(e,t.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var t=this.options.appendTo;return t=!(t=!(t=t&&(t.jquery||t.nodeType?V(t):this.document.find(t).eq(0)))||!t[0]?this.element.closest(".ui-front, dialog"):t).length?this.document[0].body:t},_initSource:function(){var i,s,n=this;Array.isArray(this.options.source)?(i=this.options.source,this.source=function(t,e){e(V.ui.autocomplete.filter(i,t.term))}):"string"==typeof this.options.source?(s=this.options.source,this.source=function(t,e){n.xhr&&n.xhr.abort(),n.xhr=V.ajax({url:s,data:t,dataType:"json",success:function(t){e(t)},error:function(){e([])}})}):this.source=this.options.source},_searchTimeout:function(s){clearTimeout(this.searching),this.searching=this._delay(function(){var t=this.term===this._value(),e=this.menu.element.is(":visible"),i=s.altKey||s.ctrlKey||s.metaKey||s.shiftKey;t&&(e||i)||(this.selectedItem=null,this.search(null,s))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length").append(V("
    ").text(e.label)).appendTo(t)},_move:function(t,e){if(this.menu.element.is(":visible"))return this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),void this.menu.blur()):void this.menu[t](e);this.search(null,e)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){this.isMultiLine&&!this.menu.element.is(":visible")||(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),V.extend(V.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(t,e){var i=new RegExp(V.ui.autocomplete.escapeRegex(e),"i");return V.grep(t,function(t){return i.test(t.label||t.value||t)})}}),V.widget("ui.autocomplete",V.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(1").text(e))},100))}});V.ui.autocomplete;var tt=/ui-corner-([a-z]){2,6}/g;V.widget("ui.controlgroup",{version:"1.13.2",defaultElement:"
    ",options:{direction:"horizontal",disabled:null,onlyVisible:!0,items:{button:"input[type=button], input[type=submit], input[type=reset], button, a",controlgroupLabel:".ui-controlgroup-label",checkboxradio:"input[type='checkbox'], input[type='radio']",selectmenu:"select",spinner:".ui-spinner-input"}},_create:function(){this._enhance()},_enhance:function(){this.element.attr("role","toolbar"),this.refresh()},_destroy:function(){this._callChildMethod("destroy"),this.childWidgets.removeData("ui-controlgroup-data"),this.element.removeAttr("role"),this.options.items.controlgroupLabel&&this.element.find(this.options.items.controlgroupLabel).find(".ui-controlgroup-label-contents").contents().unwrap()},_initWidgets:function(){var o=this,a=[];V.each(this.options.items,function(s,t){var e,n={};if(t)return"controlgroupLabel"===s?((e=o.element.find(t)).each(function(){var t=V(this);t.children(".ui-controlgroup-label-contents").length||t.contents().wrapAll("")}),o._addClass(e,null,"ui-widget ui-widget-content ui-state-default"),void(a=a.concat(e.get()))):void(V.fn[s]&&(n=o["_"+s+"Options"]?o["_"+s+"Options"]("middle"):{classes:{}},o.element.find(t).each(function(){var t=V(this),e=t[s]("instance"),i=V.widget.extend({},n);"button"===s&&t.parent(".ui-spinner").length||((e=e||t[s]()[s]("instance"))&&(i.classes=o._resolveClassesValues(i.classes,e)),t[s](i),i=t[s]("widget"),V.data(i[0],"ui-controlgroup-data",e||t[s]("instance")),a.push(i[0]))})))}),this.childWidgets=V(V.uniqueSort(a)),this._addClass(this.childWidgets,"ui-controlgroup-item")},_callChildMethod:function(e){this.childWidgets.each(function(){var t=V(this).data("ui-controlgroup-data");t&&t[e]&&t[e]()})},_updateCornerClass:function(t,e){e=this._buildSimpleOptions(e,"label").classes.label;this._removeClass(t,null,"ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all"),this._addClass(t,null,e)},_buildSimpleOptions:function(t,e){var i="vertical"===this.options.direction,s={classes:{}};return s.classes[e]={middle:"",first:"ui-corner-"+(i?"top":"left"),last:"ui-corner-"+(i?"bottom":"right"),only:"ui-corner-all"}[t],s},_spinnerOptions:function(t){t=this._buildSimpleOptions(t,"ui-spinner");return t.classes["ui-spinner-up"]="",t.classes["ui-spinner-down"]="",t},_buttonOptions:function(t){return this._buildSimpleOptions(t,"ui-button")},_checkboxradioOptions:function(t){return this._buildSimpleOptions(t,"ui-checkboxradio-label")},_selectmenuOptions:function(t){var e="vertical"===this.options.direction;return{width:e&&"auto",classes:{middle:{"ui-selectmenu-button-open":"","ui-selectmenu-button-closed":""},first:{"ui-selectmenu-button-open":"ui-corner-"+(e?"top":"tl"),"ui-selectmenu-button-closed":"ui-corner-"+(e?"top":"left")},last:{"ui-selectmenu-button-open":e?"":"ui-corner-tr","ui-selectmenu-button-closed":"ui-corner-"+(e?"bottom":"right")},only:{"ui-selectmenu-button-open":"ui-corner-top","ui-selectmenu-button-closed":"ui-corner-all"}}[t]}},_resolveClassesValues:function(i,s){var n={};return V.each(i,function(t){var e=s.options.classes[t]||"",e=String.prototype.trim.call(e.replace(tt,""));n[t]=(e+" "+i[t]).replace(/\s+/g," ")}),n},_setOption:function(t,e){"direction"===t&&this._removeClass("ui-controlgroup-"+this.options.direction),this._super(t,e),"disabled"!==t?this.refresh():this._callChildMethod(e?"disable":"enable")},refresh:function(){var n,o=this;this._addClass("ui-controlgroup ui-controlgroup-"+this.options.direction),"horizontal"===this.options.direction&&this._addClass(null,"ui-helper-clearfix"),this._initWidgets(),n=this.childWidgets,(n=this.options.onlyVisible?n.filter(":visible"):n).length&&(V.each(["first","last"],function(t,e){var i,s=n[e]().data("ui-controlgroup-data");s&&o["_"+s.widgetName+"Options"]?((i=o["_"+s.widgetName+"Options"](1===n.length?"only":e)).classes=o._resolveClassesValues(i.classes,s),s.element[s.widgetName](i)):o._updateCornerClass(n[e](),e)}),this._callChildMethod("refresh"))}});V.widget("ui.checkboxradio",[V.ui.formResetMixin,{version:"1.13.2",options:{disabled:null,label:null,icon:!0,classes:{"ui-checkboxradio-label":"ui-corner-all","ui-checkboxradio-icon":"ui-corner-all"}},_getCreateOptions:function(){var t,e=this._super()||{};return this._readType(),t=this.element.labels(),this.label=V(t[t.length-1]),this.label.length||V.error("No label found for checkboxradio widget"),this.originalLabel="",(t=this.label.contents().not(this.element[0])).length&&(this.originalLabel+=t.clone().wrapAll("
    ").parent().html()),this.originalLabel&&(e.label=this.originalLabel),null!=(t=this.element[0].disabled)&&(e.disabled=t),e},_create:function(){var t=this.element[0].checked;this._bindFormResetHandler(),null==this.options.disabled&&(this.options.disabled=this.element[0].disabled),this._setOption("disabled",this.options.disabled),this._addClass("ui-checkboxradio","ui-helper-hidden-accessible"),this._addClass(this.label,"ui-checkboxradio-label","ui-button ui-widget"),"radio"===this.type&&this._addClass(this.label,"ui-checkboxradio-radio-label"),this.options.label&&this.options.label!==this.originalLabel?this._updateLabel():this.originalLabel&&(this.options.label=this.originalLabel),this._enhance(),t&&this._addClass(this.label,"ui-checkboxradio-checked","ui-state-active"),this._on({change:"_toggleClasses",focus:function(){this._addClass(this.label,null,"ui-state-focus ui-visual-focus")},blur:function(){this._removeClass(this.label,null,"ui-state-focus ui-visual-focus")}})},_readType:function(){var t=this.element[0].nodeName.toLowerCase();this.type=this.element[0].type,"input"===t&&/radio|checkbox/.test(this.type)||V.error("Can't create checkboxradio on element.nodeName="+t+" and element.type="+this.type)},_enhance:function(){this._updateIcon(this.element[0].checked)},widget:function(){return this.label},_getRadioGroup:function(){var t=this.element[0].name,e="input[name='"+V.escapeSelector(t)+"']";return t?(this.form.length?V(this.form[0].elements).filter(e):V(e).filter(function(){return 0===V(this)._form().length})).not(this.element):V([])},_toggleClasses:function(){var t=this.element[0].checked;this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",t),this.options.icon&&"checkbox"===this.type&&this._toggleClass(this.icon,null,"ui-icon-check ui-state-checked",t)._toggleClass(this.icon,null,"ui-icon-blank",!t),"radio"===this.type&&this._getRadioGroup().each(function(){var t=V(this).checkboxradio("instance");t&&t._removeClass(t.label,"ui-checkboxradio-checked","ui-state-active")})},_destroy:function(){this._unbindFormResetHandler(),this.icon&&(this.icon.remove(),this.iconSpace.remove())},_setOption:function(t,e){if("label"!==t||e){if(this._super(t,e),"disabled"===t)return this._toggleClass(this.label,null,"ui-state-disabled",e),void(this.element[0].disabled=e);this.refresh()}},_updateIcon:function(t){var e="ui-icon ui-icon-background ";this.options.icon?(this.icon||(this.icon=V(""),this.iconSpace=V(" "),this._addClass(this.iconSpace,"ui-checkboxradio-icon-space")),"checkbox"===this.type?(e+=t?"ui-icon-check ui-state-checked":"ui-icon-blank",this._removeClass(this.icon,null,t?"ui-icon-blank":"ui-icon-check")):e+="ui-icon-blank",this._addClass(this.icon,"ui-checkboxradio-icon",e),t||this._removeClass(this.icon,null,"ui-icon-check ui-state-checked"),this.icon.prependTo(this.label).after(this.iconSpace)):void 0!==this.icon&&(this.icon.remove(),this.iconSpace.remove(),delete this.icon)},_updateLabel:function(){var t=this.label.contents().not(this.element[0]);this.icon&&(t=t.not(this.icon[0])),(t=this.iconSpace?t.not(this.iconSpace[0]):t).remove(),this.label.append(this.options.label)},refresh:function(){var t=this.element[0].checked,e=this.element[0].disabled;this._updateIcon(t),this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",t),null!==this.options.label&&this._updateLabel(),e!==this.options.disabled&&this._setOptions({disabled:e})}}]);var et;V.ui.checkboxradio;V.widget("ui.button",{version:"1.13.2",defaultElement:"
    "+(0
    ":""):"")}f+=_}return f+=F,t._keyEvent=!1,f},_generateMonthYearHeader:function(t,e,i,s,n,o,a,r){var l,h,c,u,d,p,f=this._get(t,"changeMonth"),g=this._get(t,"changeYear"),m=this._get(t,"showMonthAfterYear"),_=this._get(t,"selectMonthLabel"),v=this._get(t,"selectYearLabel"),b="
    ",y="";if(o||!f)y+=""+a[e]+"";else{for(l=s&&s.getFullYear()===i,h=n&&n.getFullYear()===i,y+=""}if(m||(b+=y+(!o&&f&&g?"":" ")),!t.yearshtml)if(t.yearshtml="",o||!g)b+=""+i+"";else{for(a=this._get(t,"yearRange").split(":"),u=(new Date).getFullYear(),d=(_=function(t){t=t.match(/c[+\-].*/)?i+parseInt(t.substring(1),10):t.match(/[+\-].*/)?u+parseInt(t,10):parseInt(t,10);return isNaN(t)?u:t})(a[0]),p=Math.max(d,_(a[1]||"")),d=s?Math.max(d,s.getFullYear()):d,p=n?Math.min(p,n.getFullYear()):p,t.yearshtml+="",b+=t.yearshtml,t.yearshtml=null}return b+=this._get(t,"yearSuffix"),m&&(b+=(!o&&f&&g?"":" ")+y),b+="
    "},_adjustInstDate:function(t,e,i){var s=t.selectedYear+("Y"===i?e:0),n=t.selectedMonth+("M"===i?e:0),e=Math.min(t.selectedDay,this._getDaysInMonth(s,n))+("D"===i?e:0),e=this._restrictMinMax(t,this._daylightSavingAdjust(new Date(s,n,e)));t.selectedDay=e.getDate(),t.drawMonth=t.selectedMonth=e.getMonth(),t.drawYear=t.selectedYear=e.getFullYear(),"M"!==i&&"Y"!==i||this._notifyChange(t)},_restrictMinMax:function(t,e){var i=this._getMinMaxDate(t,"min"),t=this._getMinMaxDate(t,"max"),e=i&&e=i.getTime())&&(!s||e.getTime()<=s.getTime())&&(!n||e.getFullYear()>=n)&&(!o||e.getFullYear()<=o)},_getFormatConfig:function(t){var e=this._get(t,"shortYearCutoff");return{shortYearCutoff:e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),dayNamesShort:this._get(t,"dayNamesShort"),dayNames:this._get(t,"dayNames"),monthNamesShort:this._get(t,"monthNamesShort"),monthNames:this._get(t,"monthNames")}},_formatDate:function(t,e,i,s){e||(t.currentDay=t.selectedDay,t.currentMonth=t.selectedMonth,t.currentYear=t.selectedYear);e=e?"object"==typeof e?e:this._daylightSavingAdjust(new Date(s,i,e)):this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return this.formatDate(this._get(t,"dateFormat"),e,this._getFormatConfig(t))}}),V.fn.datepicker=function(t){if(!this.length)return this;V.datepicker.initialized||(V(document).on("mousedown",V.datepicker._checkExternalClick),V.datepicker.initialized=!0),0===V("#"+V.datepicker._mainDivId).length&&V("body").append(V.datepicker.dpDiv);var e=Array.prototype.slice.call(arguments,1);return"string"==typeof t&&("isDisabled"===t||"getDate"===t||"widget"===t)||"option"===t&&2===arguments.length&&"string"==typeof arguments[1]?V.datepicker["_"+t+"Datepicker"].apply(V.datepicker,[this[0]].concat(e)):this.each(function(){"string"==typeof t?V.datepicker["_"+t+"Datepicker"].apply(V.datepicker,[this].concat(e)):V.datepicker._attachDatepicker(this,t)})},V.datepicker=new st,V.datepicker.initialized=!1,V.datepicker.uuid=(new Date).getTime(),V.datepicker.version="1.13.2";V.datepicker,V.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var rt=!1;V(document).on("mouseup",function(){rt=!1});V.widget("ui.mouse",{version:"1.13.2",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(t){if(!0===V.data(t.target,e.widgetName+".preventClickEvent"))return V.removeData(t.target,e.widgetName+".preventClickEvent"),t.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!rt){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var e=this,i=1===t.which,s=!("string"!=typeof this.options.cancel||!t.target.nodeName)&&V(t.target).closest(this.options.cancel).length;return i&&!s&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){e.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=!1!==this._mouseStart(t),!this._mouseStarted)?(t.preventDefault(),!0):(!0===V.data(t.target,this.widgetName+".preventClickEvent")&&V.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return e._mouseMove(t)},this._mouseUpDelegate=function(t){return e._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),rt=!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(V.ui.ie&&(!document.documentMode||document.documentMode<9)&&!t.button)return this._mouseUp(t);if(!t.which)if(t.originalEvent.altKey||t.originalEvent.ctrlKey||t.originalEvent.metaKey||t.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=!1!==this._mouseStart(this._mouseDownEvent,t),this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&V.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,rt=!1,t.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),V.ui.plugin={add:function(t,e,i){var s,n=V.ui[t].prototype;for(s in i)n.plugins[s]=n.plugins[s]||[],n.plugins[s].push([e,i[s]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;n").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var e=V.ui.safeActiveElement(this.document[0]);V(t.target).closest(e).length||V.ui.safeBlur(e)},_mouseStart:function(t){var e=this.options;return this.helper=this._createHelper(t),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),V.ui.ddmanager&&(V.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=0i[2]&&(o=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(a=i[3]+this.offset.click.top)),s.grid&&(t=s.grid[1]?this.originalPageY+Math.round((a-this.originalPageY)/s.grid[1])*s.grid[1]:this.originalPageY,a=!i||t-this.offset.click.top>=i[1]||t-this.offset.click.top>i[3]?t:t-this.offset.click.top>=i[1]?t-s.grid[1]:t+s.grid[1],t=s.grid[0]?this.originalPageX+Math.round((o-this.originalPageX)/s.grid[0])*s.grid[0]:this.originalPageX,o=!i||t-this.offset.click.left>=i[0]||t-this.offset.click.left>i[2]?t:t-this.offset.click.left>=i[0]?t-s.grid[0]:t+s.grid[0]),"y"===s.axis&&(o=this.originalPageX),"x"===s.axis&&(a=this.originalPageY)),{top:a-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:n?0:this.offset.scroll.top),left:o-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:n?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(t,e,i){return i=i||this._uiHash(),V.ui.plugin.call(this,t,[e,i,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),i.offset=this.positionAbs),V.Widget.prototype._trigger.call(this,t,e,i)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),V.ui.plugin.add("draggable","connectToSortable",{start:function(e,t,i){var s=V.extend({},t,{item:i.element});i.sortables=[],V(i.options.connectToSortable).each(function(){var t=V(this).sortable("instance");t&&!t.options.disabled&&(i.sortables.push(t),t.refreshPositions(),t._trigger("activate",e,s))})},stop:function(e,t,i){var s=V.extend({},t,{item:i.element});i.cancelHelperRemoval=!1,V.each(i.sortables,function(){var t=this;t.isOver?(t.isOver=0,i.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,s))})},drag:function(i,s,n){V.each(n.sortables,function(){var t=!1,e=this;e.positionAbs=n.positionAbs,e.helperProportions=n.helperProportions,e.offset.click=n.offset.click,e._intersectsWith(e.containerCache)&&(t=!0,V.each(n.sortables,function(){return this.positionAbs=n.positionAbs,this.helperProportions=n.helperProportions,this.offset.click=n.offset.click,t=this!==e&&this._intersectsWith(this.containerCache)&&V.contains(e.element[0],this.element[0])?!1:t})),t?(e.isOver||(e.isOver=1,n._parent=s.helper.parent(),e.currentItem=s.helper.appendTo(e.element).data("ui-sortable-item",!0),e.options._helper=e.options.helper,e.options.helper=function(){return s.helper[0]},i.target=e.currentItem[0],e._mouseCapture(i,!0),e._mouseStart(i,!0,!0),e.offset.click.top=n.offset.click.top,e.offset.click.left=n.offset.click.left,e.offset.parent.left-=n.offset.parent.left-e.offset.parent.left,e.offset.parent.top-=n.offset.parent.top-e.offset.parent.top,n._trigger("toSortable",i),n.dropped=e.element,V.each(n.sortables,function(){this.refreshPositions()}),n.currentItem=n.element,e.fromOutside=n),e.currentItem&&(e._mouseDrag(i),s.position=e.position)):e.isOver&&(e.isOver=0,e.cancelHelperRemoval=!0,e.options._revert=e.options.revert,e.options.revert=!1,e._trigger("out",i,e._uiHash(e)),e._mouseStop(i,!0),e.options.revert=e.options._revert,e.options.helper=e.options._helper,e.placeholder&&e.placeholder.remove(),s.helper.appendTo(n._parent),n._refreshOffsets(i),s.position=n._generatePosition(i,!0),n._trigger("fromSortable",i),n.dropped=!1,V.each(n.sortables,function(){this.refreshPositions()}))})}}),V.ui.plugin.add("draggable","cursor",{start:function(t,e,i){var s=V("body"),i=i.options;s.css("cursor")&&(i._cursor=s.css("cursor")),s.css("cursor",i.cursor)},stop:function(t,e,i){i=i.options;i._cursor&&V("body").css("cursor",i._cursor)}}),V.ui.plugin.add("draggable","opacity",{start:function(t,e,i){e=V(e.helper),i=i.options;e.css("opacity")&&(i._opacity=e.css("opacity")),e.css("opacity",i.opacity)},stop:function(t,e,i){i=i.options;i._opacity&&V(e.helper).css("opacity",i._opacity)}}),V.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(t,e,i){var s=i.options,n=!1,o=i.scrollParentNotHidden[0],a=i.document[0];o!==a&&"HTML"!==o.tagName?(s.axis&&"x"===s.axis||(i.overflowOffset.top+o.offsetHeight-t.pageY
    ").css({overflow:"hidden",position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,t={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(t),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(t),this._proportionallyResize()),this._setupHandles(),e.autoHide&&V(this.element).on("mouseenter",function(){e.disabled||(i._removeClass("ui-resizable-autohide"),i._handles.show())}).on("mouseleave",function(){e.disabled||i.resizing||(i._addClass("ui-resizable-autohide"),i._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy(),this._addedHandles.remove();function t(t){V(t).removeData("resizable").removeData("ui-resizable").off(".resizable")}var e;return this.elementIsWrapper&&(t(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;case"aspectRatio":this._aspectRatio=!!e}},_setupHandles:function(){var t,e,i,s,n,o=this.options,a=this;if(this.handles=o.handles||(V(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=V(),this._addedHandles=V(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),i=this.handles.split(","),this.handles={},e=0;e"),this._addClass(n,"ui-resizable-handle "+s),n.css({zIndex:o.zIndex}),this.handles[t]=".ui-resizable-"+t,this.element.children(this.handles[t]).length||(this.element.append(n),this._addedHandles=this._addedHandles.add(n));this._renderAxis=function(t){var e,i,s;for(e in t=t||this.element,this.handles)this.handles[e].constructor===String?this.handles[e]=this.element.children(this.handles[e]).first().show():(this.handles[e].jquery||this.handles[e].nodeType)&&(this.handles[e]=V(this.handles[e]),this._on(this.handles[e],{mousedown:a._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(i=V(this.handles[e],this.element),s=/sw|ne|nw|se|n|s/.test(e)?i.outerHeight():i.outerWidth(),i=["padding",/ne|nw|n/.test(e)?"Top":/se|sw|s/.test(e)?"Bottom":/^e$/.test(e)?"Right":"Left"].join(""),t.css(i,s),this._proportionallyResize()),this._handles=this._handles.add(this.handles[e])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){a.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),a.axis=n&&n[1]?n[1]:"se")}),o.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._addedHandles.remove()},_mouseCapture:function(t){var e,i,s=!1;for(e in this.handles)(i=V(this.handles[e])[0])!==t.target&&!V.contains(i,t.target)||(s=!0);return!this.options.disabled&&s},_mouseStart:function(t){var e,i,s=this.options,n=this.element;return this.resizing=!0,this._renderProxy(),e=this._num(this.helper.css("left")),i=this._num(this.helper.css("top")),s.containment&&(e+=V(s.containment).scrollLeft()||0,i+=V(s.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:e,top:i},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:n.width(),height:n.height()},this.originalSize=this._helper?{width:n.outerWidth(),height:n.outerHeight()}:{width:n.width(),height:n.height()},this.sizeDiff={width:n.outerWidth()-n.width(),height:n.outerHeight()-n.height()},this.originalPosition={left:e,top:i},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof s.aspectRatio?s.aspectRatio:this.originalSize.width/this.originalSize.height||1,s=V(".ui-resizable-"+this.axis).css("cursor"),V("body").css("cursor","auto"===s?this.axis+"-resize":s),this._addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var e=this.originalMousePosition,i=this.axis,s=t.pageX-e.left||0,e=t.pageY-e.top||0,i=this._change[i];return this._updatePrevProperties(),i&&(e=i.apply(this,[t,s,e]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(e=this._updateRatio(e,t)),e=this._respectSize(e,t),this._updateCache(e),this._propagate("resize",t),e=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),V.isEmptyObject(e)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges())),!1},_mouseStop:function(t){this.resizing=!1;var e,i,s,n=this.options,o=this;return this._helper&&(s=(e=(i=this._proportionallyResizeElements).length&&/textarea/i.test(i[0].nodeName))&&this._hasScroll(i[0],"left")?0:o.sizeDiff.height,i=e?0:o.sizeDiff.width,e={width:o.helper.width()-i,height:o.helper.height()-s},i=parseFloat(o.element.css("left"))+(o.position.left-o.originalPosition.left)||null,s=parseFloat(o.element.css("top"))+(o.position.top-o.originalPosition.top)||null,n.animate||this.element.css(V.extend(e,{top:s,left:i})),o.helper.height(o.size.height),o.helper.width(o.size.width),this._helper&&!n.animate&&this._proportionallyResize()),V("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s=this.options,n={minWidth:this._isNumber(s.minWidth)?s.minWidth:0,maxWidth:this._isNumber(s.maxWidth)?s.maxWidth:1/0,minHeight:this._isNumber(s.minHeight)?s.minHeight:0,maxHeight:this._isNumber(s.maxHeight)?s.maxHeight:1/0};(this._aspectRatio||t)&&(e=n.minHeight*this.aspectRatio,i=n.minWidth/this.aspectRatio,s=n.maxHeight*this.aspectRatio,t=n.maxWidth/this.aspectRatio,e>n.minWidth&&(n.minWidth=e),i>n.minHeight&&(n.minHeight=i),st.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,l=this.originalPosition.top+this.originalSize.height,h=/sw|nw|w/.test(i),i=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&h&&(t.left=r-e.minWidth),s&&h&&(t.left=r-e.maxWidth),a&&i&&(t.top=l-e.minHeight),n&&i&&(t.top=l-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];e<4;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;e
    ").css({overflow:"hidden"}),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++e.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize;return{left:this.originalPosition.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize;return{top:this.originalPosition.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(t,e,i){return V.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},sw:function(t,e,i){return V.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,e,i]))},ne:function(t,e,i){return V.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},nw:function(t,e,i){return V.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,e,i]))}},_propagate:function(t,e){V.ui.plugin.call(this,t,[e,this.ui()]),"resize"!==t&&this._trigger(t,e,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),V.ui.plugin.add("resizable","animate",{stop:function(e){var i=V(this).resizable("instance"),t=i.options,s=i._proportionallyResizeElements,n=s.length&&/textarea/i.test(s[0].nodeName),o=n&&i._hasScroll(s[0],"left")?0:i.sizeDiff.height,a=n?0:i.sizeDiff.width,n={width:i.size.width-a,height:i.size.height-o},a=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,o=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(V.extend(n,o&&a?{top:o,left:a}:{}),{duration:t.animateDuration,easing:t.animateEasing,step:function(){var t={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};s&&s.length&&V(s[0]).css({width:t.width,height:t.height}),i._updateCache(t),i._propagate("resize",e)}})}}),V.ui.plugin.add("resizable","containment",{start:function(){var i,s,n=V(this).resizable("instance"),t=n.options,e=n.element,o=t.containment,a=o instanceof V?o.get(0):/parent/.test(o)?e.parent().get(0):o;a&&(n.containerElement=V(a),/document/.test(o)||o===document?(n.containerOffset={left:0,top:0},n.containerPosition={left:0,top:0},n.parentData={element:V(document),left:0,top:0,width:V(document).width(),height:V(document).height()||document.body.parentNode.scrollHeight}):(i=V(a),s=[],V(["Top","Right","Left","Bottom"]).each(function(t,e){s[t]=n._num(i.css("padding"+e))}),n.containerOffset=i.offset(),n.containerPosition=i.position(),n.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},t=n.containerOffset,e=n.containerSize.height,o=n.containerSize.width,o=n._hasScroll(a,"left")?a.scrollWidth:o,e=n._hasScroll(a)?a.scrollHeight:e,n.parentData={element:a,left:t.left,top:t.top,width:o,height:e}))},resize:function(t){var e=V(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.position,o=e._aspectRatio||t.shiftKey,a={top:0,left:0},r=e.containerElement,t=!0;r[0]!==document&&/static/.test(r.css("position"))&&(a=s),n.left<(e._helper?s.left:0)&&(e.size.width=e.size.width+(e._helper?e.position.left-s.left:e.position.left-a.left),o&&(e.size.height=e.size.width/e.aspectRatio,t=!1),e.position.left=i.helper?s.left:0),n.top<(e._helper?s.top:0)&&(e.size.height=e.size.height+(e._helper?e.position.top-s.top:e.position.top),o&&(e.size.width=e.size.height*e.aspectRatio,t=!1),e.position.top=e._helper?s.top:0),i=e.containerElement.get(0)===e.element.parent().get(0),n=/relative|absolute/.test(e.containerElement.css("position")),i&&n?(e.offset.left=e.parentData.left+e.position.left,e.offset.top=e.parentData.top+e.position.top):(e.offset.left=e.element.offset().left,e.offset.top=e.element.offset().top),n=Math.abs(e.sizeDiff.width+(e._helper?e.offset.left-a.left:e.offset.left-s.left)),s=Math.abs(e.sizeDiff.height+(e._helper?e.offset.top-a.top:e.offset.top-s.top)),n+e.size.width>=e.parentData.width&&(e.size.width=e.parentData.width-n,o&&(e.size.height=e.size.width/e.aspectRatio,t=!1)),s+e.size.height>=e.parentData.height&&(e.size.height=e.parentData.height-s,o&&(e.size.width=e.size.height*e.aspectRatio,t=!1)),t||(e.position.left=e.prevPosition.left,e.position.top=e.prevPosition.top,e.size.width=e.prevSize.width,e.size.height=e.prevSize.height)},stop:function(){var t=V(this).resizable("instance"),e=t.options,i=t.containerOffset,s=t.containerPosition,n=t.containerElement,o=V(t.helper),a=o.offset(),r=o.outerWidth()-t.sizeDiff.width,o=o.outerHeight()-t.sizeDiff.height;t._helper&&!e.animate&&/relative/.test(n.css("position"))&&V(this).css({left:a.left-s.left-i.left,width:r,height:o}),t._helper&&!e.animate&&/static/.test(n.css("position"))&&V(this).css({left:a.left-s.left-i.left,width:r,height:o})}}),V.ui.plugin.add("resizable","alsoResize",{start:function(){var t=V(this).resizable("instance").options;V(t.alsoResize).each(function(){var t=V(this);t.data("ui-resizable-alsoresize",{width:parseFloat(t.width()),height:parseFloat(t.height()),left:parseFloat(t.css("left")),top:parseFloat(t.css("top"))})})},resize:function(t,i){var e=V(this).resizable("instance"),s=e.options,n=e.originalSize,o=e.originalPosition,a={height:e.size.height-n.height||0,width:e.size.width-n.width||0,top:e.position.top-o.top||0,left:e.position.left-o.left||0};V(s.alsoResize).each(function(){var t=V(this),s=V(this).data("ui-resizable-alsoresize"),n={},e=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];V.each(e,function(t,e){var i=(s[e]||0)+(a[e]||0);i&&0<=i&&(n[e]=i||null)}),t.css(n)})},stop:function(){V(this).removeData("ui-resizable-alsoresize")}}),V.ui.plugin.add("resizable","ghost",{start:function(){var t=V(this).resizable("instance"),e=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}),t._addClass(t.ghost,"ui-resizable-ghost"),!1!==V.uiBackCompat&&"string"==typeof t.options.ghost&&t.ghost.addClass(this.options.ghost),t.ghost.appendTo(t.helper)},resize:function(){var t=V(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=V(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),V.ui.plugin.add("resizable","grid",{resize:function(){var t,e=V(this).resizable("instance"),i=e.options,s=e.size,n=e.originalSize,o=e.originalPosition,a=e.axis,r="number"==typeof i.grid?[i.grid,i.grid]:i.grid,l=r[0]||1,h=r[1]||1,c=Math.round((s.width-n.width)/l)*l,u=Math.round((s.height-n.height)/h)*h,d=n.width+c,p=n.height+u,f=i.maxWidth&&i.maxWidthd,s=i.minHeight&&i.minHeight>p;i.grid=r,m&&(d+=l),s&&(p+=h),f&&(d-=l),g&&(p-=h),/^(se|s|e)$/.test(a)?(e.size.width=d,e.size.height=p):/^(ne)$/.test(a)?(e.size.width=d,e.size.height=p,e.position.top=o.top-u):/^(sw)$/.test(a)?(e.size.width=d,e.size.height=p,e.position.left=o.left-c):((p-h<=0||d-l<=0)&&(t=e._getPaddingPlusBorderDimensions(this)),0=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",s+1),i=!0),i&&!e&&this._trigger("focus",t),i},open:function(){var t=this;this._isOpen?this._moveToTop()&&this._focusTabbable():(this._isOpen=!0,this.opener=V(V.ui.safeActiveElement(this.document[0])),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){t._focusTabbable(),t._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"))},_focusTabbable:function(){var t=this._focusedElement;(t=!(t=!(t=!(t=!(t=t||this.element.find("[autofocus]")).length?this.element.find(":tabbable"):t).length?this.uiDialogButtonPane.find(":tabbable"):t).length?this.uiDialogTitlebarClose.filter(":tabbable"):t).length?this.uiDialog:t).eq(0).trigger("focus")},_restoreTabbableFocus:function(){var t=V.ui.safeActiveElement(this.document[0]);this.uiDialog[0]===t||V.contains(this.uiDialog[0],t)||this._focusTabbable()},_keepFocus:function(t){t.preventDefault(),this._restoreTabbableFocus(),this._delay(this._restoreTabbableFocus)},_createWrapper:function(){this.uiDialog=V("
    ").hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._addClass(this.uiDialog,"ui-dialog","ui-widget ui-widget-content ui-front"),this._on(this.uiDialog,{keydown:function(t){if(this.options.closeOnEscape&&!t.isDefaultPrevented()&&t.keyCode&&t.keyCode===V.ui.keyCode.ESCAPE)return t.preventDefault(),void this.close(t);var e,i,s;t.keyCode!==V.ui.keyCode.TAB||t.isDefaultPrevented()||(e=this.uiDialog.find(":tabbable"),i=e.first(),s=e.last(),t.target!==s[0]&&t.target!==this.uiDialog[0]||t.shiftKey?t.target!==i[0]&&t.target!==this.uiDialog[0]||!t.shiftKey||(this._delay(function(){s.trigger("focus")}),t.preventDefault()):(this._delay(function(){i.trigger("focus")}),t.preventDefault()))},mousedown:function(t){this._moveToTop(t)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var t;this.uiDialogTitlebar=V("
    "),this._addClass(this.uiDialogTitlebar,"ui-dialog-titlebar","ui-widget-header ui-helper-clearfix"),this._on(this.uiDialogTitlebar,{mousedown:function(t){V(t.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.trigger("focus")}}),this.uiDialogTitlebarClose=V("").button({label:V("").text(this.options.closeText).html(),icon:"ui-icon-closethick",showLabel:!1}).appendTo(this.uiDialogTitlebar),this._addClass(this.uiDialogTitlebarClose,"ui-dialog-titlebar-close"),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}}),t=V("").uniqueId().prependTo(this.uiDialogTitlebar),this._addClass(t,"ui-dialog-title"),this._title(t),this.uiDialogTitlebar.prependTo(this.uiDialog),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(t){this.options.title?t.text(this.options.title):t.html(" ")},_createButtonPane:function(){this.uiDialogButtonPane=V("
    "),this._addClass(this.uiDialogButtonPane,"ui-dialog-buttonpane","ui-widget-content ui-helper-clearfix"),this.uiButtonSet=V("
    ").appendTo(this.uiDialogButtonPane),this._addClass(this.uiButtonSet,"ui-dialog-buttonset"),this._createButtons()},_createButtons:function(){var s=this,t=this.options.buttons;this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),V.isEmptyObject(t)||Array.isArray(t)&&!t.length?this._removeClass(this.uiDialog,"ui-dialog-buttons"):(V.each(t,function(t,e){var i;e=V.extend({type:"button"},e="function"==typeof e?{click:e,text:t}:e),i=e.click,t={icon:e.icon,iconPosition:e.iconPosition,showLabel:e.showLabel,icons:e.icons,text:e.text},delete e.click,delete e.icon,delete e.iconPosition,delete e.showLabel,delete e.icons,"boolean"==typeof e.text&&delete e.text,V("",e).button(t).appendTo(s.uiButtonSet).on("click",function(){i.apply(s.element[0],arguments)})}),this._addClass(this.uiDialog,"ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog))},_makeDraggable:function(){var n=this,o=this.options;function a(t){return{position:t.position,offset:t.offset}}this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(t,e){n._addClass(V(this),"ui-dialog-dragging"),n._blockFrames(),n._trigger("dragStart",t,a(e))},drag:function(t,e){n._trigger("drag",t,a(e))},stop:function(t,e){var i=e.offset.left-n.document.scrollLeft(),s=e.offset.top-n.document.scrollTop();o.position={my:"left top",at:"left"+(0<=i?"+":"")+i+" top"+(0<=s?"+":"")+s,of:n.window},n._removeClass(V(this),"ui-dialog-dragging"),n._unblockFrames(),n._trigger("dragStop",t,a(e))}})},_makeResizable:function(){var n=this,o=this.options,t=o.resizable,e=this.uiDialog.css("position"),t="string"==typeof t?t:"n,e,s,w,se,sw,ne,nw";function a(t){return{originalPosition:t.originalPosition,originalSize:t.originalSize,position:t.position,size:t.size}}this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:o.maxWidth,maxHeight:o.maxHeight,minWidth:o.minWidth,minHeight:this._minHeight(),handles:t,start:function(t,e){n._addClass(V(this),"ui-dialog-resizing"),n._blockFrames(),n._trigger("resizeStart",t,a(e))},resize:function(t,e){n._trigger("resize",t,a(e))},stop:function(t,e){var i=n.uiDialog.offset(),s=i.left-n.document.scrollLeft(),i=i.top-n.document.scrollTop();o.height=n.uiDialog.height(),o.width=n.uiDialog.width(),o.position={my:"left top",at:"left"+(0<=s?"+":"")+s+" top"+(0<=i?"+":"")+i,of:n.window},n._removeClass(V(this),"ui-dialog-resizing"),n._unblockFrames(),n._trigger("resizeStop",t,a(e))}}).css("position",e)},_trackFocus:function(){this._on(this.widget(),{focusin:function(t){this._makeFocusTarget(),this._focusedElement=V(t.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var t=this._trackingInstances(),e=V.inArray(this,t);-1!==e&&t.splice(e,1)},_trackingInstances:function(){var t=this.document.data("ui-dialog-instances");return t||this.document.data("ui-dialog-instances",t=[]),t},_minHeight:function(){var t=this.options;return"auto"===t.height?t.minHeight:Math.min(t.minHeight,t.height)},_position:function(){var t=this.uiDialog.is(":visible");t||this.uiDialog.show(),this.uiDialog.position(this.options.position),t||this.uiDialog.hide()},_setOptions:function(t){var i=this,s=!1,n={};V.each(t,function(t,e){i._setOption(t,e),t in i.sizeRelatedOptions&&(s=!0),t in i.resizableRelatedOptions&&(n[t]=e)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(t,e){var i,s=this.uiDialog;"disabled"!==t&&(this._super(t,e),"appendTo"===t&&this.uiDialog.appendTo(this._appendTo()),"buttons"===t&&this._createButtons(),"closeText"===t&&this.uiDialogTitlebarClose.button({label:V("").text(""+this.options.closeText).html()}),"draggable"===t&&((i=s.is(":data(ui-draggable)"))&&!e&&s.draggable("destroy"),!i&&e&&this._makeDraggable()),"position"===t&&this._position(),"resizable"===t&&((i=s.is(":data(ui-resizable)"))&&!e&&s.resizable("destroy"),i&&"string"==typeof e&&s.resizable("option","handles",e),i||!1===e||this._makeResizable()),"title"===t&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var t,e,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),t=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),e=Math.max(0,s.minHeight-t),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-t):"none","auto"===s.height?this.element.css({minHeight:e,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-t)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=V(this);return V("
    ").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return!!V(t.target).closest(".ui-dialog").length||!!V(t.target).closest(".ui-datepicker").length},_createOverlay:function(){var i,s;this.options.modal&&(i=V.fn.jquery.substring(0,4),s=!0,this._delay(function(){s=!1}),this.document.data("ui-dialog-overlays")||this.document.on("focusin.ui-dialog",function(t){var e;s||((e=this._trackingInstances()[0])._allowInteraction(t)||(t.preventDefault(),e._focusTabbable(),"3.4."!==i&&"3.5."!==i||e._delay(e._restoreTabbableFocus)))}.bind(this)),this.overlay=V("
    ").appendTo(this._appendTo()),this._addClass(this.overlay,null,"ui-widget-overlay ui-front"),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1))},_destroyOverlay:function(){var t;this.options.modal&&this.overlay&&((t=this.document.data("ui-dialog-overlays")-1)?this.document.data("ui-dialog-overlays",t):(this.document.off("focusin.ui-dialog"),this.document.removeData("ui-dialog-overlays")),this.overlay.remove(),this.overlay=null)}}),!1!==V.uiBackCompat&&V.widget("ui.dialog",V.ui.dialog,{options:{dialogClass:""},_createWrapper:function(){this._super(),this.uiDialog.addClass(this.options.dialogClass)},_setOption:function(t,e){"dialogClass"===t&&this.uiDialog.removeClass(this.options.dialogClass).addClass(e),this._superApply(arguments)}});V.ui.dialog;function lt(t,e,i){return e<=t&&t").appendTo(this.element),this._addClass(this.valueDiv,"ui-progressbar-value","ui-widget-header"),this._refreshValue()},_destroy:function(){this.element.removeAttr("role aria-valuemin aria-valuemax aria-valuenow"),this.valueDiv.remove()},value:function(t){if(void 0===t)return this.options.value;this.options.value=this._constrainedValue(t),this._refreshValue()},_constrainedValue:function(t){return void 0===t&&(t=this.options.value),this.indeterminate=!1===t,"number"!=typeof t&&(t=0),!this.indeterminate&&Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var t=this.options.value,e=this._percentage();this.valueDiv.toggle(this.indeterminate||t>this.min).width(e.toFixed(0)+"%"),this._toggleClass(this.valueDiv,"ui-progressbar-complete",null,t===this.options.max)._toggleClass("ui-progressbar-indeterminate",null,this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=V("
    ").appendTo(this.valueDiv),this._addClass(this.overlayDiv,"ui-progressbar-overlay"))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":t}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==t&&(this.oldValue=t,this._trigger("change")),t===this.options.max&&this._trigger("complete")}}),V.widget("ui.selectable",V.ui.mouse,{version:"1.13.2",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var i=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){i.elementPos=V(i.element[0]).offset(),i.selectees=V(i.options.filter,i.element[0]),i._addClass(i.selectees,"ui-selectee"),i.selectees.each(function(){var t=V(this),e=t.offset(),e={left:e.left-i.elementPos.left,top:e.top-i.elementPos.top};V.data(this,"selectable-item",{element:this,$element:t,left:e.left,top:e.top,right:e.left+t.outerWidth(),bottom:e.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=V("
    "),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(i){var s=this,t=this.options;this.opos=[i.pageX,i.pageY],this.elementPos=V(this.element[0]).offset(),this.options.disabled||(this.selectees=V(t.filter,this.element[0]),this._trigger("start",i),V(t.appendTo).append(this.helper),this.helper.css({left:i.pageX,top:i.pageY,width:0,height:0}),t.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var t=V.data(this,"selectable-item");t.startselected=!0,i.metaKey||i.ctrlKey||(s._removeClass(t.$element,"ui-selected"),t.selected=!1,s._addClass(t.$element,"ui-unselecting"),t.unselecting=!0,s._trigger("unselecting",i,{unselecting:t.element}))}),V(i.target).parents().addBack().each(function(){var t,e=V.data(this,"selectable-item");if(e)return t=!i.metaKey&&!i.ctrlKey||!e.$element.hasClass("ui-selected"),s._removeClass(e.$element,t?"ui-unselecting":"ui-selected")._addClass(e.$element,t?"ui-selecting":"ui-unselecting"),e.unselecting=!t,e.selecting=t,(e.selected=t)?s._trigger("selecting",i,{selecting:e.element}):s._trigger("unselecting",i,{unselecting:e.element}),!1}))},_mouseDrag:function(s){if(this.dragged=!0,!this.options.disabled){var t,n=this,o=this.options,a=this.opos[0],r=this.opos[1],l=s.pageX,h=s.pageY;return ll||i.righth||i.bottoma&&i.rightr&&i.bottom",options:{appendTo:null,classes:{"ui-selectmenu-button-open":"ui-corner-top","ui-selectmenu-button-closed":"ui-corner-all"},disabled:null,icons:{button:"ui-icon-triangle-1-s"},position:{my:"left top",at:"left bottom",collision:"none"},width:!1,change:null,close:null,focus:null,open:null,select:null},_create:function(){var t=this.element.uniqueId().attr("id");this.ids={element:t,button:t+"-button",menu:t+"-menu"},this._drawButton(),this._drawMenu(),this._bindFormResetHandler(),this._rendered=!1,this.menuItems=V()},_drawButton:function(){var t,e=this,i=this._parseOption(this.element.find("option:selected"),this.element[0].selectedIndex);this.labels=this.element.labels().attr("for",this.ids.button),this._on(this.labels,{click:function(t){this.button.trigger("focus"),t.preventDefault()}}),this.element.hide(),this.button=V("",{tabindex:this.options.disabled?-1:0,id:this.ids.button,role:"combobox","aria-expanded":"false","aria-autocomplete":"list","aria-owns":this.ids.menu,"aria-haspopup":"true",title:this.element.attr("title")}).insertAfter(this.element),this._addClass(this.button,"ui-selectmenu-button ui-selectmenu-button-closed","ui-button ui-widget"),t=V("").appendTo(this.button),this._addClass(t,"ui-selectmenu-icon","ui-icon "+this.options.icons.button),this.buttonItem=this._renderButtonItem(i).appendTo(this.button),!1!==this.options.width&&this._resizeButton(),this._on(this.button,this._buttonEvents),this.button.one("focusin",function(){e._rendered||e._refreshMenu()})},_drawMenu:function(){var i=this;this.menu=V("
      ",{"aria-hidden":"true","aria-labelledby":this.ids.button,id:this.ids.menu}),this.menuWrap=V("
      ").append(this.menu),this._addClass(this.menuWrap,"ui-selectmenu-menu","ui-front"),this.menuWrap.appendTo(this._appendTo()),this.menuInstance=this.menu.menu({classes:{"ui-menu":"ui-corner-bottom"},role:"listbox",select:function(t,e){t.preventDefault(),i._setSelection(),i._select(e.item.data("ui-selectmenu-item"),t)},focus:function(t,e){e=e.item.data("ui-selectmenu-item");null!=i.focusIndex&&e.index!==i.focusIndex&&(i._trigger("focus",t,{item:e}),i.isOpen||i._select(e,t)),i.focusIndex=e.index,i.button.attr("aria-activedescendant",i.menuItems.eq(e.index).attr("id"))}}).menu("instance"),this.menuInstance._off(this.menu,"mouseleave"),this.menuInstance._closeOnDocumentClick=function(){return!1},this.menuInstance._isDivider=function(){return!1}},refresh:function(){this._refreshMenu(),this.buttonItem.replaceWith(this.buttonItem=this._renderButtonItem(this._getSelectedItem().data("ui-selectmenu-item")||{})),null===this.options.width&&this._resizeButton()},_refreshMenu:function(){var t=this.element.find("option");this.menu.empty(),this._parseOptions(t),this._renderMenu(this.menu,this.items),this.menuInstance.refresh(),this.menuItems=this.menu.find("li").not(".ui-selectmenu-optgroup").find(".ui-menu-item-wrapper"),this._rendered=!0,t.length&&(t=this._getSelectedItem(),this.menuInstance.focus(null,t),this._setAria(t.data("ui-selectmenu-item")),this._setOption("disabled",this.element.prop("disabled")))},open:function(t){this.options.disabled||(this._rendered?(this._removeClass(this.menu.find(".ui-state-active"),null,"ui-state-active"),this.menuInstance.focus(null,this._getSelectedItem())):this._refreshMenu(),this.menuItems.length&&(this.isOpen=!0,this._toggleAttr(),this._resizeMenu(),this._position(),this._on(this.document,this._documentClick),this._trigger("open",t)))},_position:function(){this.menuWrap.position(V.extend({of:this.button},this.options.position))},close:function(t){this.isOpen&&(this.isOpen=!1,this._toggleAttr(),this.range=null,this._off(this.document),this._trigger("close",t))},widget:function(){return this.button},menuWidget:function(){return this.menu},_renderButtonItem:function(t){var e=V("");return this._setText(e,t.label),this._addClass(e,"ui-selectmenu-text"),e},_renderMenu:function(s,t){var n=this,o="";V.each(t,function(t,e){var i;e.optgroup!==o&&(i=V("
    • ",{text:e.optgroup}),n._addClass(i,"ui-selectmenu-optgroup","ui-menu-divider"+(e.element.parent("optgroup").prop("disabled")?" ui-state-disabled":"")),i.appendTo(s),o=e.optgroup),n._renderItemData(s,e)})},_renderItemData:function(t,e){return this._renderItem(t,e).data("ui-selectmenu-item",e)},_renderItem:function(t,e){var i=V("
    • "),s=V("
      ",{title:e.element.attr("title")});return e.disabled&&this._addClass(i,null,"ui-state-disabled"),this._setText(s,e.label),i.append(s).appendTo(t)},_setText:function(t,e){e?t.text(e):t.html(" ")},_move:function(t,e){var i,s=".ui-menu-item";this.isOpen?i=this.menuItems.eq(this.focusIndex).parent("li"):(i=this.menuItems.eq(this.element[0].selectedIndex).parent("li"),s+=":not(.ui-state-disabled)"),(s="first"===t||"last"===t?i["first"===t?"prevAll":"nextAll"](s).eq(-1):i[t+"All"](s).eq(0)).length&&this.menuInstance.focus(e,s)},_getSelectedItem:function(){return this.menuItems.eq(this.element[0].selectedIndex).parent("li")},_toggle:function(t){this[this.isOpen?"close":"open"](t)},_setSelection:function(){var t;this.range&&(window.getSelection?((t=window.getSelection()).removeAllRanges(),t.addRange(this.range)):this.range.select(),this.button.trigger("focus"))},_documentClick:{mousedown:function(t){this.isOpen&&(V(t.target).closest(".ui-selectmenu-menu, #"+V.escapeSelector(this.ids.button)).length||this.close(t))}},_buttonEvents:{mousedown:function(){var t;window.getSelection?(t=window.getSelection()).rangeCount&&(this.range=t.getRangeAt(0)):this.range=document.selection.createRange()},click:function(t){this._setSelection(),this._toggle(t)},keydown:function(t){var e=!0;switch(t.keyCode){case V.ui.keyCode.TAB:case V.ui.keyCode.ESCAPE:this.close(t),e=!1;break;case V.ui.keyCode.ENTER:this.isOpen&&this._selectFocusedItem(t);break;case V.ui.keyCode.UP:t.altKey?this._toggle(t):this._move("prev",t);break;case V.ui.keyCode.DOWN:t.altKey?this._toggle(t):this._move("next",t);break;case V.ui.keyCode.SPACE:this.isOpen?this._selectFocusedItem(t):this._toggle(t);break;case V.ui.keyCode.LEFT:this._move("prev",t);break;case V.ui.keyCode.RIGHT:this._move("next",t);break;case V.ui.keyCode.HOME:case V.ui.keyCode.PAGE_UP:this._move("first",t);break;case V.ui.keyCode.END:case V.ui.keyCode.PAGE_DOWN:this._move("last",t);break;default:this.menu.trigger(t),e=!1}e&&t.preventDefault()}},_selectFocusedItem:function(t){var e=this.menuItems.eq(this.focusIndex).parent("li");e.hasClass("ui-state-disabled")||this._select(e.data("ui-selectmenu-item"),t)},_select:function(t,e){var i=this.element[0].selectedIndex;this.element[0].selectedIndex=t.index,this.buttonItem.replaceWith(this.buttonItem=this._renderButtonItem(t)),this._setAria(t),this._trigger("select",e,{item:t}),t.index!==i&&this._trigger("change",e,{item:t}),this.close(e)},_setAria:function(t){t=this.menuItems.eq(t.index).attr("id");this.button.attr({"aria-labelledby":t,"aria-activedescendant":t}),this.menu.attr("aria-activedescendant",t)},_setOption:function(t,e){var i;"icons"===t&&(i=this.button.find("span.ui-icon"),this._removeClass(i,null,this.options.icons.button)._addClass(i,null,e.button)),this._super(t,e),"appendTo"===t&&this.menuWrap.appendTo(this._appendTo()),"width"===t&&this._resizeButton()},_setOptionDisabled:function(t){this._super(t),this.menuInstance.option("disabled",t),this.button.attr("aria-disabled",t),this._toggleClass(this.button,null,"ui-state-disabled",t),this.element.prop("disabled",t),t?(this.button.attr("tabindex",-1),this.close()):this.button.attr("tabindex",0)},_appendTo:function(){var t=this.options.appendTo;return t=!(t=!(t=t&&(t.jquery||t.nodeType?V(t):this.document.find(t).eq(0)))||!t[0]?this.element.closest(".ui-front, dialog"):t).length?this.document[0].body:t},_toggleAttr:function(){this.button.attr("aria-expanded",this.isOpen),this._removeClass(this.button,"ui-selectmenu-button-"+(this.isOpen?"closed":"open"))._addClass(this.button,"ui-selectmenu-button-"+(this.isOpen?"open":"closed"))._toggleClass(this.menuWrap,"ui-selectmenu-open",null,this.isOpen),this.menu.attr("aria-hidden",!this.isOpen)},_resizeButton:function(){var t=this.options.width;!1!==t?(null===t&&(t=this.element.show().outerWidth(),this.element.hide()),this.button.outerWidth(t)):this.button.css("width","")},_resizeMenu:function(){this.menu.outerWidth(Math.max(this.button.outerWidth(),this.menu.width("").outerWidth()+1))},_getCreateOptions:function(){var t=this._super();return t.disabled=this.element.prop("disabled"),t},_parseOptions:function(t){var i=this,s=[];t.each(function(t,e){e.hidden||s.push(i._parseOption(V(e),t))}),this.items=s},_parseOption:function(t,e){var i=t.parent("optgroup");return{element:t,index:e,value:t.val(),label:t.text(),optgroup:i.attr("label")||"",disabled:i.prop("disabled")||t.prop("disabled")}},_destroy:function(){this._unbindFormResetHandler(),this.menuWrap.remove(),this.button.remove(),this.element.show(),this.element.removeUniqueId(),this.labels.attr("for",this.ids.element)}}]),V.widget("ui.slider",V.ui.mouse,{version:"1.13.2",widgetEventPrefix:"slide",options:{animate:!1,classes:{"ui-slider":"ui-corner-all","ui-slider-handle":"ui-corner-all","ui-slider-range":"ui-corner-all ui-widget-header"},distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},numPages:5,_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this._calculateNewMax(),this._addClass("ui-slider ui-slider-"+this.orientation,"ui-widget ui-widget-content"),this._refresh(),this._animateOff=!1},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var t,e=this.options,i=this.element.find(".ui-slider-handle"),s=[],n=e.values&&e.values.length||1;for(i.length>n&&(i.slice(n).remove(),i=i.slice(0,n)),t=i.length;t");this.handles=i.add(V(s.join("")).appendTo(this.element)),this._addClass(this.handles,"ui-slider-handle","ui-state-default"),this.handle=this.handles.eq(0),this.handles.each(function(t){V(this).data("ui-slider-handle-index",t).attr("tabIndex",0)})},_createRange:function(){var t=this.options;t.range?(!0===t.range&&(t.values?t.values.length&&2!==t.values.length?t.values=[t.values[0],t.values[0]]:Array.isArray(t.values)&&(t.values=t.values.slice(0)):t.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?(this._removeClass(this.range,"ui-slider-range-min ui-slider-range-max"),this.range.css({left:"",bottom:""})):(this.range=V("
      ").appendTo(this.element),this._addClass(this.range,"ui-slider-range")),"min"!==t.range&&"max"!==t.range||this._addClass(this.range,"ui-slider-range-"+t.range)):(this.range&&this.range.remove(),this.range=null)},_setupEvents:function(){this._off(this.handles),this._on(this.handles,this._handleEvents),this._hoverable(this.handles),this._focusable(this.handles)},_destroy:function(){this.handles.remove(),this.range&&this.range.remove(),this._mouseDestroy()},_mouseCapture:function(t){var i,s,n,o,e,a,r=this,l=this.options;return!l.disabled&&(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),a={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(a),s=this._valueMax()-this._valueMin()+1,this.handles.each(function(t){var e=Math.abs(i-r.values(t));(e=this._valueMax())return this._valueMax();var e=0=e&&(t+=0this.options.max&&(t-=i),this.max=parseFloat(t.toFixed(this._precision()))},_precision:function(){var t=this._precisionOf(this.options.step);return t=null!==this.options.min?Math.max(t,this._precisionOf(this.options.min)):t},_precisionOf:function(t){var e=t.toString(),t=e.indexOf(".");return-1===t?0:e.length-t-1},_valueMin:function(){return this.options.min},_valueMax:function(){return this.max},_refreshRange:function(t){"vertical"===t&&this.range.css({width:"",left:""}),"horizontal"===t&&this.range.css({height:"",bottom:""})},_refreshValue:function(){var e,i,t,s,n,o=this.options.range,a=this.options,r=this,l=!this._animateOff&&a.animate,h={};this._hasMultipleValues()?this.handles.each(function(t){i=(r.values(t)-r._valueMin())/(r._valueMax()-r._valueMin())*100,h["horizontal"===r.orientation?"left":"bottom"]=i+"%",V(this).stop(1,1)[l?"animate":"css"](h,a.animate),!0===r.options.range&&("horizontal"===r.orientation?(0===t&&r.range.stop(1,1)[l?"animate":"css"]({left:i+"%"},a.animate),1===t&&r.range[l?"animate":"css"]({width:i-e+"%"},{queue:!1,duration:a.animate})):(0===t&&r.range.stop(1,1)[l?"animate":"css"]({bottom:i+"%"},a.animate),1===t&&r.range[l?"animate":"css"]({height:i-e+"%"},{queue:!1,duration:a.animate}))),e=i}):(t=this.value(),s=this._valueMin(),n=this._valueMax(),i=n!==s?(t-s)/(n-s)*100:0,h["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[l?"animate":"css"](h,a.animate),"min"===o&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:i+"%"},a.animate),"max"===o&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:100-i+"%"},a.animate),"min"===o&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:i+"%"},a.animate),"max"===o&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:100-i+"%"},a.animate))},_handleEvents:{keydown:function(t){var e,i,s,n=V(t.target).data("ui-slider-handle-index");switch(t.keyCode){case V.ui.keyCode.HOME:case V.ui.keyCode.END:case V.ui.keyCode.PAGE_UP:case V.ui.keyCode.PAGE_DOWN:case V.ui.keyCode.UP:case V.ui.keyCode.RIGHT:case V.ui.keyCode.DOWN:case V.ui.keyCode.LEFT:if(t.preventDefault(),!this._keySliding&&(this._keySliding=!0,this._addClass(V(t.target),null,"ui-state-active"),!1===this._start(t,n)))return}switch(s=this.options.step,e=i=this._hasMultipleValues()?this.values(n):this.value(),t.keyCode){case V.ui.keyCode.HOME:i=this._valueMin();break;case V.ui.keyCode.END:i=this._valueMax();break;case V.ui.keyCode.PAGE_UP:i=this._trimAlignValue(e+(this._valueMax()-this._valueMin())/this.numPages);break;case V.ui.keyCode.PAGE_DOWN:i=this._trimAlignValue(e-(this._valueMax()-this._valueMin())/this.numPages);break;case V.ui.keyCode.UP:case V.ui.keyCode.RIGHT:if(e===this._valueMax())return;i=this._trimAlignValue(e+s);break;case V.ui.keyCode.DOWN:case V.ui.keyCode.LEFT:if(e===this._valueMin())return;i=this._trimAlignValue(e-s)}this._slide(t,n,i)},keyup:function(t){var e=V(t.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(t,e),this._change(t,e),this._removeClass(V(t.target),null,"ui-state-active"))}}}),V.widget("ui.sortable",V.ui.mouse,{version:"1.13.2",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(t,e,i){return e<=t&&t*{ cursor: "+o.cursor+" !important; }").appendTo(n)),o.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",o.zIndex)),o.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",o.opacity)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!i)for(s=this.containers.length-1;0<=s;s--)this.containers[s]._trigger("activate",t,this._uiHash(this));return V.ui.ddmanager&&(V.ui.ddmanager.current=this),V.ui.ddmanager&&!o.dropBehaviour&&V.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this._addClass(this.helper,"ui-sortable-helper"),this.helper.parent().is(this.appendTo)||(this.helper.detach().appendTo(this.appendTo),this.offset.parent=this._getParentOffset()),this.position=this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,this.lastPositionAbs=this.positionAbs=this._convertPositionTo("absolute"),this._mouseDrag(t),!0},_scroll:function(t){var e=this.options,i=!1;return this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageYt[this.floating?"width":"height"]?h&&c:o",i.document[0]);return i._addClass(t,"ui-sortable-placeholder",s||i.currentItem[0].className)._removeClass(t,"ui-sortable-helper"),"tbody"===n?i._createTrPlaceholder(i.currentItem.find("tr").eq(0),V("",i.document[0]).appendTo(t)):"tr"===n?i._createTrPlaceholder(i.currentItem,t):"img"===n&&t.attr("src",i.currentItem.attr("src")),s||t.css("visibility","hidden"),t},update:function(t,e){s&&!o.forcePlaceholderSize||(e.height()&&(!o.forcePlaceholderSize||"tbody"!==n&&"tr"!==n)||e.height(i.currentItem.innerHeight()-parseInt(i.currentItem.css("paddingTop")||0,10)-parseInt(i.currentItem.css("paddingBottom")||0,10)),e.width()||e.width(i.currentItem.innerWidth()-parseInt(i.currentItem.css("paddingLeft")||0,10)-parseInt(i.currentItem.css("paddingRight")||0,10)))}}),i.placeholder=V(o.placeholder.element.call(i.element,i.currentItem)),i.currentItem.after(i.placeholder),o.placeholder.update(i,i.placeholder)},_createTrPlaceholder:function(t,e){var i=this;t.children().each(function(){V(" ",i.document[0]).attr("colspan",V(this).attr("colspan")||1).appendTo(e)})},_contactContainers:function(t){for(var e,i,s,n,o,a,r,l,h,c=null,u=null,d=this.containers.length-1;0<=d;d--)V.contains(this.currentItem[0],this.containers[d].element[0])||(this._intersectsWith(this.containers[d].containerCache)?c&&V.contains(this.containers[d].element[0],c.element[0])||(c=this.containers[d],u=d):this.containers[d].containerCache.over&&(this.containers[d]._trigger("out",t,this._uiHash(this)),this.containers[d].containerCache.over=0));if(c)if(1===this.containers.length)this.containers[u].containerCache.over||(this.containers[u]._trigger("over",t,this._uiHash(this)),this.containers[u].containerCache.over=1);else{for(i=1e4,s=null,n=(l=c.floating||this._isFloating(this.currentItem))?"left":"top",o=l?"width":"height",h=l?"pageX":"pageY",e=this.items.length-1;0<=e;e--)V.contains(this.containers[u].element[0],this.items[e].item[0])&&this.items[e].item[0]!==this.currentItem[0]&&(a=this.items[e].item.offset()[n],r=!1,t[h]-a>this.items[e][o]/2&&(r=!0),Math.abs(t[h]-a)this.containment[2]&&(i=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(s=this.containment[3]+this.offset.click.top)),e.grid&&(t=this.originalPageY+Math.round((s-this.originalPageY)/e.grid[1])*e.grid[1],s=!this.containment||t-this.offset.click.top>=this.containment[1]&&t-this.offset.click.top<=this.containment[3]?t:t-this.offset.click.top>=this.containment[1]?t-e.grid[1]:t+e.grid[1],t=this.originalPageX+Math.round((i-this.originalPageX)/e.grid[0])*e.grid[0],i=!this.containment||t-this.offset.click.left>=this.containment[0]&&t-this.offset.click.left<=this.containment[2]?t:t-this.offset.click.left>=this.containment[0]?t-e.grid[0]:t+e.grid[0])),{top:s-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():o?0:n.scrollTop()),left:i-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():o?0:n.scrollLeft())}},_rearrange:function(t,e,i,s){i?i[0].appendChild(this.placeholder[0]):e.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?e.item[0]:e.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(t,e){this.reverting=!1;var i,s=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(i in this._storedCSS)"auto"!==this._storedCSS[i]&&"static"!==this._storedCSS[i]||(this._storedCSS[i]="");this.currentItem.css(this._storedCSS),this._removeClass(this.currentItem,"ui-sortable-helper")}else this.currentItem.show();function n(e,i,s){return function(t){s._trigger(e,t,i._uiHash(i))}}for(this.fromOutside&&!e&&s.push(function(t){this._trigger("receive",t,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||e||s.push(function(t){this._trigger("update",t,this._uiHash())}),this!==this.currentContainer&&(e||(s.push(function(t){this._trigger("remove",t,this._uiHash())}),s.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),s.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer)))),i=this.containers.length-1;0<=i;i--)e||s.push(n("deactivate",this,this.containers[i])),this.containers[i].containerCache.over&&(s.push(n("out",this,this.containers[i])),this.containers[i].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,e||this._trigger("beforeStop",t,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!e){for(i=0;i",widgetEventPrefix:"spin",options:{classes:{"ui-spinner":"ui-corner-all","ui-spinner-down":"ui-corner-br","ui-spinner-up":"ui-corner-tr"},culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var s=this._super(),n=this.element;return V.each(["min","max","step"],function(t,e){var i=n.attr(e);null!=i&&i.length&&(s[e]=i)}),s},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){this.cancelBlur?delete this.cancelBlur:(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t))},mousewheel:function(t,e){var i=V.ui.safeActiveElement(this.document[0]);if(this.element[0]===i&&e){if(!this.spinning&&!this._start(t))return!1;this._spin((0").parent().append("")},_draw:function(){this._enhance(),this._addClass(this.uiSpinner,"ui-spinner","ui-widget ui-widget-content"),this._addClass("ui-spinner-input"),this.element.attr("role","spinbutton"),this.buttons=this.uiSpinner.children("a").attr("tabIndex",-1).attr("aria-hidden",!0).button({classes:{"ui-button":""}}),this._removeClass(this.buttons,"ui-corner-all"),this._addClass(this.buttons.first(),"ui-spinner-button ui-spinner-up"),this._addClass(this.buttons.last(),"ui-spinner-button ui-spinner-down"),this.buttons.first().button({icon:this.options.icons.up,showLabel:!1}),this.buttons.last().button({icon:this.options.icons.down,showLabel:!1}),this.buttons.height()>Math.ceil(.5*this.uiSpinner.height())&&0e.max?e.max:null!==e.min&&t"},_buttonHtml:function(){return""}});var ct;V.ui.spinner;V.widget("ui.tabs",{version:"1.13.2",delay:300,options:{active:null,classes:{"ui-tabs":"ui-corner-all","ui-tabs-nav":"ui-corner-all","ui-tabs-panel":"ui-corner-bottom","ui-tabs-tab":"ui-corner-top"},collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:(ct=/#.*$/,function(t){var e=t.href.replace(ct,""),i=location.href.replace(ct,"");try{e=decodeURIComponent(e)}catch(t){}try{i=decodeURIComponent(i)}catch(t){}return 1?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,e=this.tablist.children(":has(a[href])");t.disabled=V.map(e.filter(".ui-state-disabled"),function(t){return e.index(t)}),this._processTabs(),!1!==t.active&&this.anchors.length?this.active.length&&!V.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=V()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active):(t.active=!1,this.active=V()),this._refresh()},_refresh:function(){this._setOptionDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._addClass(this.active,"ui-tabs-active","ui-state-active"),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var l=this,t=this.tabs,e=this.anchors,i=this.panels;this.tablist=this._getList().attr("role","tablist"),this._addClass(this.tablist,"ui-tabs-nav","ui-helper-reset ui-helper-clearfix ui-widget-header"),this.tablist.on("mousedown"+this.eventNamespace,"> li",function(t){V(this).is(".ui-state-disabled")&&t.preventDefault()}).on("focus"+this.eventNamespace,".ui-tabs-anchor",function(){V(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").attr({role:"tab",tabIndex:-1}),this._addClass(this.tabs,"ui-tabs-tab","ui-state-default"),this.anchors=this.tabs.map(function(){return V("a",this)[0]}).attr({tabIndex:-1}),this._addClass(this.anchors,"ui-tabs-anchor"),this.panels=V(),this.anchors.each(function(t,e){var i,s,n,o=V(e).uniqueId().attr("id"),a=V(e).closest("li"),r=a.attr("aria-controls");l._isLocal(e)?(n=(i=e.hash).substring(1),s=l.element.find(l._sanitizeSelector(i))):(n=a.attr("aria-controls")||V({}).uniqueId()[0].id,(s=l.element.find(i="#"+n)).length||(s=l._createPanel(n)).insertAfter(l.panels[t-1]||l.tablist),s.attr("aria-live","polite")),s.length&&(l.panels=l.panels.add(s)),r&&a.data("ui-tabs-aria-controls",r),a.attr({"aria-controls":n,"aria-labelledby":o}),s.attr("aria-labelledby",o)}),this.panels.attr("role","tabpanel"),this._addClass(this.panels,"ui-tabs-panel","ui-widget-content"),t&&(this._off(t.not(this.tabs)),this._off(e.not(this.anchors)),this._off(i.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol, ul").eq(0)},_createPanel:function(t){return V("
      ").attr("id",t).data("ui-tabs-destroy",!0)},_setOptionDisabled:function(t){var e,i;for(Array.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1),i=0;e=this.tabs[i];i++)e=V(e),!0===t||-1!==V.inArray(i,t)?(e.attr("aria-disabled","true"),this._addClass(e,null,"ui-state-disabled")):(e.removeAttr("aria-disabled"),this._removeClass(e,null,"ui-state-disabled"));this.options.disabled=t,this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!0===t)},_setupEvents:function(t){var i={};t&&V.each(t.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(t){t.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var i,e=this.element.parent();"fill"===t?(i=e.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var t=V(this),e=t.css("position");"absolute"!==e&&"fixed"!==e&&(i-=t.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=V(this).outerHeight(!0)}),this.panels.each(function(){V(this).height(Math.max(0,i-V(this).innerHeight()+V(this).height()))}).css("overflow","auto")):"auto"===t&&(i=0,this.panels.each(function(){i=Math.max(i,V(this).height("").height())}).height(i))},_eventHandler:function(t){var e=this.options,i=this.active,s=V(t.currentTarget).closest("li"),n=s[0]===i[0],o=n&&e.collapsible,a=o?V():this._getPanelForTab(s),r=i.length?this._getPanelForTab(i):V(),i={oldTab:i,oldPanel:r,newTab:o?V():s,newPanel:a};t.preventDefault(),s.hasClass("ui-state-disabled")||s.hasClass("ui-tabs-loading")||this.running||n&&!e.collapsible||!1===this._trigger("beforeActivate",t,i)||(e.active=!o&&this.tabs.index(s),this.active=n?V():s,this.xhr&&this.xhr.abort(),r.length||a.length||V.error("jQuery UI Tabs: Mismatching fragment identifier."),a.length&&this.load(this.tabs.index(s),t),this._toggle(t,i))},_toggle:function(t,e){var i=this,s=e.newPanel,n=e.oldPanel;function o(){i.running=!1,i._trigger("activate",t,e)}function a(){i._addClass(e.newTab.closest("li"),"ui-tabs-active","ui-state-active"),s.length&&i.options.show?i._show(s,i.options.show,o):(s.show(),o())}this.running=!0,n.length&&this.options.hide?this._hide(n,this.options.hide,function(){i._removeClass(e.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),a()}):(this._removeClass(e.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),n.hide(),a()),n.attr("aria-hidden","true"),e.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),s.length&&n.length?e.oldTab.attr("tabIndex",-1):s.length&&this.tabs.filter(function(){return 0===V(this).attr("tabIndex")}).attr("tabIndex",-1),s.attr("aria-hidden","false"),e.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(t){var t=this._findActive(t);t[0]!==this.active[0]&&(t=(t=!t.length?this.active:t).find(".ui-tabs-anchor")[0],this._eventHandler({target:t,currentTarget:t,preventDefault:V.noop}))},_findActive:function(t){return!1===t?V():this.tabs.eq(t)},_getIndex:function(t){return t="string"==typeof t?this.anchors.index(this.anchors.filter("[href$='"+V.escapeSelector(t)+"']")):t},_destroy:function(){this.xhr&&this.xhr.abort(),this.tablist.removeAttr("role").off(this.eventNamespace),this.anchors.removeAttr("role tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){V.data(this,"ui-tabs-destroy")?V(this).remove():V(this).removeAttr("role tabIndex aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded")}),this.tabs.each(function(){var t=V(this),e=t.data("ui-tabs-aria-controls");e?t.attr("aria-controls",e).removeData("ui-tabs-aria-controls"):t.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(i){var t=this.options.disabled;!1!==t&&(t=void 0!==i&&(i=this._getIndex(i),Array.isArray(t)?V.map(t,function(t){return t!==i?t:null}):V.map(this.tabs,function(t,e){return e!==i?e:null})),this._setOptionDisabled(t))},disable:function(t){var e=this.options.disabled;if(!0!==e){if(void 0===t)e=!0;else{if(t=this._getIndex(t),-1!==V.inArray(t,e))return;e=Array.isArray(e)?V.merge([t],e).sort():[t]}this._setOptionDisabled(e)}},load:function(t,s){t=this._getIndex(t);function n(t,e){"abort"===e&&o.panels.stop(!1,!0),o._removeClass(i,"ui-tabs-loading"),a.removeAttr("aria-busy"),t===o.xhr&&delete o.xhr}var o=this,i=this.tabs.eq(t),t=i.find(".ui-tabs-anchor"),a=this._getPanelForTab(i),r={tab:i,panel:a};this._isLocal(t[0])||(this.xhr=V.ajax(this._ajaxSettings(t,s,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(this._addClass(i,"ui-tabs-loading"),a.attr("aria-busy","true"),this.xhr.done(function(t,e,i){setTimeout(function(){a.html(t),o._trigger("load",s,r),n(i,e)},1)}).fail(function(t,e){setTimeout(function(){n(t,e)},1)})))},_ajaxSettings:function(t,i,s){var n=this;return{url:t.attr("href").replace(/#.*$/,""),beforeSend:function(t,e){return n._trigger("beforeLoad",i,V.extend({jqXHR:t,ajaxSettings:e},s))}}},_getPanelForTab:function(t){t=V(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+t))}}),!1!==V.uiBackCompat&&V.widget("ui.tabs",V.ui.tabs,{_processTabs:function(){this._superApply(arguments),this._addClass(this.tabs,"ui-tab")}});V.ui.tabs;V.widget("ui.tooltip",{version:"1.13.2",options:{classes:{"ui-tooltip":"ui-corner-all ui-widget-shadow"},content:function(){var t=V(this).attr("title");return V("").text(t).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,track:!1,close:null,open:null},_addDescribedBy:function(t,e){var i=(t.attr("aria-describedby")||"").split(/\s+/);i.push(e),t.data("ui-tooltip-id",e).attr("aria-describedby",String.prototype.trim.call(i.join(" ")))},_removeDescribedBy:function(t){var e=t.data("ui-tooltip-id"),i=(t.attr("aria-describedby")||"").split(/\s+/),e=V.inArray(e,i);-1!==e&&i.splice(e,1),t.removeData("ui-tooltip-id"),(i=String.prototype.trim.call(i.join(" ")))?t.attr("aria-describedby",i):t.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.liveRegion=V("
      ").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this.disabledTitles=V([])},_setOption:function(t,e){var i=this;this._super(t,e),"content"===t&&V.each(this.tooltips,function(t,e){i._updateContent(e.element)})},_setOptionDisabled:function(t){this[t?"_disable":"_enable"]()},_disable:function(){var s=this;V.each(this.tooltips,function(t,e){var i=V.Event("blur");i.target=i.currentTarget=e.element[0],s.close(i,!0)}),this.disabledTitles=this.disabledTitles.add(this.element.find(this.options.items).addBack().filter(function(){var t=V(this);if(t.is("[title]"))return t.data("ui-tooltip-title",t.attr("title")).removeAttr("title")}))},_enable:function(){this.disabledTitles.each(function(){var t=V(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))}),this.disabledTitles=V([])},open:function(t){var i=this,e=V(t?t.target:this.element).closest(this.options.items);e.length&&!e.data("ui-tooltip-id")&&(e.attr("title")&&e.data("ui-tooltip-title",e.attr("title")),e.data("ui-tooltip-open",!0),t&&"mouseover"===t.type&&e.parents().each(function(){var t,e=V(this);e.data("ui-tooltip-open")&&((t=V.Event("blur")).target=t.currentTarget=this,i.close(t,!0)),e.attr("title")&&(e.uniqueId(),i.parents[this.id]={element:this,title:e.attr("title")},e.attr("title",""))}),this._registerCloseHandlers(t,e),this._updateContent(e,t))},_updateContent:function(e,i){var t=this.options.content,s=this,n=i?i.type:null;if("string"==typeof t||t.nodeType||t.jquery)return this._open(i,e,t);(t=t.call(e[0],function(t){s._delay(function(){e.data("ui-tooltip-open")&&(i&&(i.type=n),this._open(i,e,t))})}))&&this._open(i,e,t)},_open:function(t,e,i){var s,n,o,a=V.extend({},this.options.position);function r(t){a.of=t,n.is(":hidden")||n.position(a)}i&&((s=this._find(e))?s.tooltip.find(".ui-tooltip-content").html(i):(e.is("[title]")&&(t&&"mouseover"===t.type?e.attr("title",""):e.removeAttr("title")),s=this._tooltip(e),n=s.tooltip,this._addDescribedBy(e,n.attr("id")),n.find(".ui-tooltip-content").html(i),this.liveRegion.children().hide(),(i=V("
      ").html(n.find(".ui-tooltip-content").html())).removeAttr("name").find("[name]").removeAttr("name"),i.removeAttr("id").find("[id]").removeAttr("id"),i.appendTo(this.liveRegion),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:r}),r(t)):n.position(V.extend({of:e},this.options.position)),n.hide(),this._show(n,this.options.show),this.options.track&&this.options.show&&this.options.show.delay&&(o=this.delayedShow=setInterval(function(){n.is(":visible")&&(r(a.of),clearInterval(o))},13)),this._trigger("open",t,{tooltip:n})))},_registerCloseHandlers:function(t,e){var i={keyup:function(t){t.keyCode===V.ui.keyCode.ESCAPE&&((t=V.Event(t)).currentTarget=e[0],this.close(t,!0))}};e[0]!==this.element[0]&&(i.remove=function(){var t=this._find(e);t&&this._removeTooltip(t.tooltip)}),t&&"mouseover"!==t.type||(i.mouseleave="close"),t&&"focusin"!==t.type||(i.focusout="close"),this._on(!0,e,i)},close:function(t){var e,i=this,s=V(t?t.currentTarget:this.element),n=this._find(s);n?(e=n.tooltip,n.closing||(clearInterval(this.delayedShow),s.data("ui-tooltip-title")&&!s.attr("title")&&s.attr("title",s.data("ui-tooltip-title")),this._removeDescribedBy(s),n.hiding=!0,e.stop(!0),this._hide(e,this.options.hide,function(){i._removeTooltip(V(this))}),s.removeData("ui-tooltip-open"),this._off(s,"mouseleave focusout keyup"),s[0]!==this.element[0]&&this._off(s,"remove"),this._off(this.document,"mousemove"),t&&"mouseleave"===t.type&&V.each(this.parents,function(t,e){V(e.element).attr("title",e.title),delete i.parents[t]}),n.closing=!0,this._trigger("close",t,{tooltip:e}),n.hiding||(n.closing=!1))):s.removeData("ui-tooltip-open")},_tooltip:function(t){var e=V("
      ").attr("role","tooltip"),i=V("
      ").appendTo(e),s=e.uniqueId().attr("id");return this._addClass(i,"ui-tooltip-content"),this._addClass(e,"ui-tooltip","ui-widget ui-widget-content"),e.appendTo(this._appendTo(t)),this.tooltips[s]={element:t,tooltip:e}},_find:function(t){t=t.data("ui-tooltip-id");return t?this.tooltips[t]:null},_removeTooltip:function(t){clearInterval(this.delayedShow),t.remove(),delete this.tooltips[t.attr("id")]},_appendTo:function(t){t=t.closest(".ui-front, dialog");return t=!t.length?this.document[0].body:t},_destroy:function(){var s=this;V.each(this.tooltips,function(t,e){var i=V.Event("blur"),e=e.element;i.target=i.currentTarget=e[0],s.close(i,!0),V("#"+t).remove(),e.data("ui-tooltip-title")&&(e.attr("title")||e.attr("title",e.data("ui-tooltip-title")),e.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}}),!1!==V.uiBackCompat&&V.widget("ui.tooltip",V.ui.tooltip,{options:{tooltipClass:null},_tooltip:function(){var t=this._superApply(arguments);return this.options.tooltipClass&&t.tooltip.addClass(this.options.tooltipClass),t}});V.ui.tooltip}); \ No newline at end of file diff --git a/Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js b/Resources/Public/JavaScript/Frontend/jquery.min.js similarity index 100% rename from Resources/Public/JavaScript/Frontend/jquery-3.7.1.min.js rename to Resources/Public/JavaScript/Frontend/jquery.min.js diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f8b1e5e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "xm-formcycle", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "xm-formcycle", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "jquery": "^3.7.1", + "jquery-ui": "^1.13.2" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/jquery-ui": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.2.tgz", + "integrity": "sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==", + "dependencies": { + "jquery": ">=1.8.0 <4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..543de33 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "xm-formcycle", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "copy:jquery": "cp node_modules/jquery/dist/jquery.min.js Resources/Public/JavaScript/Frontend", + "copy:jqueryUi": "cp node_modules/jquery-ui/dist/jquery-ui.min.js Resources/Public/JavaScript/Frontend", + "copy:response": "cp node_modules/jquery-ui/dist/jquery-ui.min.js Resources/Public/JavaScript/Frontend", + "build": "npm run copy:jquery & npm run copy:jqueryUi" + }, + "author": "", + "license": "ISC", + "dependencies": { + "jquery": "^3.7.1", + "jquery-ui": "^1.13.2" + } +} From 2f33bb036d8418654463cc943e13003ad91367e4 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Mon, 4 Mar 2024 10:19:56 +0100 Subject: [PATCH 43/66] feat: add CSP configuration --- .../EventListener/FrontendListener.php | 34 ++++++++++++++++++ Classes/Service/FormcycleService.php | 6 ++++ Configuration/ContentSecurityPolicies.php | 36 +++++++++++++++++++ Configuration/Services.yaml | 4 +++ 4 files changed, 80 insertions(+) create mode 100644 Classes/ContentSecurityPolicy/EventListener/FrontendListener.php create mode 100644 Configuration/ContentSecurityPolicies.php diff --git a/Classes/ContentSecurityPolicy/EventListener/FrontendListener.php b/Classes/ContentSecurityPolicy/EventListener/FrontendListener.php new file mode 100644 index 0000000..fc8ed4e --- /dev/null +++ b/Classes/ContentSecurityPolicy/EventListener/FrontendListener.php @@ -0,0 +1,34 @@ +scope->type->isBackend()) { + return; + } + + $formcycleUrl = $this->formcycleService->getCspUrl(); + + $event->getCurrentPolicy()->extend( + Directive::ConnectSrc, + new UriValue($formcycleUrl), + ); + + $event->getCurrentPolicy()->extend( + Directive::FrameSrc, + new UriValue($formcycleUrl), + ); + } +} diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 5e675bb..adf873b 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -118,6 +118,12 @@ public function getAdminUrl(): string return $this->configuration->getAdminUrl(); } + public function getCspUrl(): string + { + $adminUrl = $this->getAdminUrl(); + return pathinfo($adminUrl, PATHINFO_DIRNAME); + } + public function getIframeUrl(ElementSettings $settings): string { $url = sprintf('%s/form/provide/%s', $this->configuration->getFormCycleUrl(), $settings->formId); diff --git a/Configuration/ContentSecurityPolicies.php b/Configuration/ContentSecurityPolicies.php new file mode 100644 index 0000000..0a4b511 --- /dev/null +++ b/Configuration/ContentSecurityPolicies.php @@ -0,0 +1,36 @@ + Date: Mon, 4 Mar 2024 13:44:11 +0100 Subject: [PATCH 44/66] feat: add unit test for ElementSettings --- README.md | 2 +- Tests/Unit/Dto/ElementSettingsTest.php | 26 +++++++++++++++++++ Tests/Unit/Dto/FormcycleConfigurationTest.php | 11 +++++++- Tests/Unit/Dto/IntegrationModeTest.php | 17 ++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 Tests/Unit/Dto/ElementSettingsTest.php create mode 100644 Tests/Unit/Dto/IntegrationModeTest.php diff --git a/README.md b/README.md index 3de842d..8c17961 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ them into your TYPO3 site. ## Requirements -* The Formcycle plugin `FormList` musst be installed +* Formcycle version 8 + installed plugin `Formularliste` * PHP 8.1+ ## Installation diff --git a/Tests/Unit/Dto/ElementSettingsTest.php b/Tests/Unit/Dto/ElementSettingsTest.php new file mode 100644 index 0000000..5f7fe32 --- /dev/null +++ b/Tests/Unit/Dto/ElementSettingsTest.php @@ -0,0 +1,26 @@ +getAccessibleMock(ContentObjectRenderer::class); + $flexFormService = $this->getAccessibleMock(FlexFormService::class); + $settings = ElementSettings::createFromContentElement($flexFormService, $cObj); + self::assertEquals(0, $settings->successPid); + self::assertEquals(0, $settings->errorPid); + self::assertEquals('', $settings->formId); + self::assertTrue($settings->loadFormcycleJquery); + self::assertFalse($settings->loadFormcycleJqueryUi); + self::assertFalse($settings->loadResponseJs); + self::assertEquals('', $settings->additionalParameters); + self::assertNull($settings->integrationMode); + } +} diff --git a/Tests/Unit/Dto/FormcycleConfigurationTest.php b/Tests/Unit/Dto/FormcycleConfigurationTest.php index 75eea94..8c83d1e 100644 --- a/Tests/Unit/Dto/FormcycleConfigurationTest.php +++ b/Tests/Unit/Dto/FormcycleConfigurationTest.php @@ -2,6 +2,7 @@ namespace Xima\XmFormcycle\Tests\Unit\Dto; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; use Xima\XmFormcycle\Dto\FormcycleConfiguration; use Xima\XmFormcycle\Dto\IntegrationMode; @@ -41,7 +42,7 @@ public function testInvalidFormcycleFrontendUrl(): void { $this->expectException(FormcycleConfigurationException::class); $this->expectExceptionCode(1709052152); - $this->validExtensionConfiguration['formCycleFrontendUrl'] = 'x'; + $this->validExtensionConfiguration['formCycleFrontendUrl'] = 'invalid url'; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } @@ -74,4 +75,12 @@ public function testDefaultIntegrationMode(): void $config = FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); self::assertEquals(IntegrationMode::Integrated, $config->getIntegrationMode()); } + + public function testValidFormcycleUrls() + { + $config = FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); + self::assertTrue(GeneralUtility::isValidUrl($config->getAdminUrl()), 'Valid admin url'); + self::assertTrue(GeneralUtility::isValidUrl($config->getFormListUrl()), 'Valid form list url'); + self::assertTrue(GeneralUtility::isValidUrl($config->getFormCycleUrl()), 'Valid form cycle url'); + } } diff --git a/Tests/Unit/Dto/IntegrationModeTest.php b/Tests/Unit/Dto/IntegrationModeTest.php new file mode 100644 index 0000000..67d9557 --- /dev/null +++ b/Tests/Unit/Dto/IntegrationModeTest.php @@ -0,0 +1,17 @@ +forDataProcessing()); + self::assertEquals(IntegrationMode::Ajax, IntegrationMode::AjaxFormcycle->forDataProcessing()); + self::assertEquals(IntegrationMode::Integrated, IntegrationMode::Integrated->forDataProcessing()); + self::assertEquals(IntegrationMode::Iframe, IntegrationMode::Iframe->forDataProcessing()); + } +} From 5105523020a27630d20da5e3c66eaa44d87c74d2 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Mon, 4 Mar 2024 14:39:46 +0100 Subject: [PATCH 45/66] fix: uksort return type, fluid_styled_content template paths, flexform default value --- Classes/Form/Element/FormcycleSelection.php | 5 ++++- Configuration/FlexForms/flexform_list.xml | 2 +- Configuration/TypoScript/setup.typoscript | 12 ++++++------ Tests/Unit/Form/Element/FormcycleSelectionTest.php | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Classes/Form/Element/FormcycleSelection.php b/Classes/Form/Element/FormcycleSelection.php index 7464205..5844fdf 100644 --- a/Classes/Form/Element/FormcycleSelection.php +++ b/Classes/Form/Element/FormcycleSelection.php @@ -73,7 +73,10 @@ public static function groupForms(array $forms): array } // sort "others" group (index=0) to end of array uksort($groupedForms, static function ($a, $b) { - return $b === 0 ? -1 : $a > $b; + if ($b === 0) { + return -1; + } + return $a > $b ? 0 : 1; }); return $groupedForms; } diff --git a/Configuration/FlexForms/flexform_list.xml b/Configuration/FlexForms/flexform_list.xml index 29ce64b..5b1b493 100644 --- a/Configuration/FlexForms/flexform_list.xml +++ b/Configuration/FlexForms/flexform_list.xml @@ -41,7 +41,7 @@ pages 1 1 - 0 + 0 diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript index 2da53c1..50a8669 100644 --- a/Configuration/TypoScript/setup.typoscript +++ b/Configuration/TypoScript/setup.typoscript @@ -1,11 +1,11 @@ tt_content.formcycle =< lib.contentElement tt_content.formcycle { - templateRootPaths.0 = EXT:xm_formcycle/Resources/Private/Templates/ - templateRootPaths.1 = {$plugin.tx_xmformcycle.view.templateRootPath} - partialRootPaths.0 = EXT:xm_formcycle/Resources/Private/Partials/ - partialRootPaths.1 = {$plugin.tx_xmformcycle.view.partialRootPath} - layoutRootPaths.0 = EXT:xm_formcycle/Resources/Private/Layouts/ - layoutRootPaths.1 = {$plugin.tx_xmformcycle.view.layoutRootPath} + templateRootPaths.1 = EXT:xm_formcycle/Resources/Private/Templates/ + templateRootPaths.2 = {$plugin.tx_xmformcycle.view.templateRootPath} + partialRootPaths.1 = EXT:xm_formcycle/Resources/Private/Partials/ + partialRootPaths.2 = {$plugin.tx_xmformcycle.view.partialRootPath} + layoutRootPaths.1 = EXT:xm_formcycle/Resources/Private/Layouts/ + layoutRootPaths.2 = {$plugin.tx_xmformcycle.view.layoutRootPath} templateName = Formcycle diff --git a/Tests/Unit/Form/Element/FormcycleSelectionTest.php b/Tests/Unit/Form/Element/FormcycleSelectionTest.php index a12f09c..3228d92 100644 --- a/Tests/Unit/Form/Element/FormcycleSelectionTest.php +++ b/Tests/Unit/Form/Element/FormcycleSelectionTest.php @@ -7,7 +7,7 @@ class FormcycleSelectionTest extends TestCase { - public function testGroupForms() + public function testGroupForms(): void { self::assertEmpty(FormcycleSelection::groupForms([])); From 29ead30dad1244d0f329616da91dcacc3b39098f Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Mon, 4 Mar 2024 15:29:27 +0100 Subject: [PATCH 46/66] fix: move ajax middleware after tsfe, remove package.lock --- .gitignore | 1 + Configuration/RequestMiddlewares.php | 2 +- package-lock.json | 30 ---------------------------- 3 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index 4847cac..8c83d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ config var vendor/ composer.lock +package.lock diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php index 6fd2bdf..609b8c1 100644 --- a/Configuration/RequestMiddlewares.php +++ b/Configuration/RequestMiddlewares.php @@ -4,7 +4,7 @@ 'frontend' => [ 'xm-formcycle/form' => [ 'target' => \Xima\XmFormcycle\Middleware\FormMiddleware::class, - 'before' => [ + 'after' => [ 'typo3/cms-frontend/tsfe', ], ], diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index f8b1e5e..0000000 --- a/package-lock.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "xm-formcycle", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "xm-formcycle", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "jquery": "^3.7.1", - "jquery-ui": "^1.13.2" - } - }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" - }, - "node_modules/jquery-ui": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.2.tgz", - "integrity": "sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==", - "dependencies": { - "jquery": ">=1.8.0 <4.0.0" - } - } - } -} From ae6621e489c572ba6b4ba8d98e68a5b51facead4 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 5 Mar 2024 18:11:17 +0100 Subject: [PATCH 47/66] feat: use FormListJson endpoint, adjust extension configuration --- Classes/Dto/FormcycleConfiguration.php | 44 ++++--------------- Tests/Unit/Dto/FormcycleConfigurationTest.php | 35 ++------------- ext_conf_template.txt | 11 +---- 3 files changed, 14 insertions(+), 76 deletions(-) diff --git a/Classes/Dto/FormcycleConfiguration.php b/Classes/Dto/FormcycleConfiguration.php index 656e973..c9c361e 100644 --- a/Classes/Dto/FormcycleConfiguration.php +++ b/Classes/Dto/FormcycleConfiguration.php @@ -9,15 +9,9 @@ final class FormcycleConfiguration { private string $formCycleUrl; - private string $formCycleFrontendUrl; - - private string $formCycleUser; - - private string $formCyclePass; - private IntegrationMode $integrationMode; - private string $formCycleClientId; + private string $formcycleClientId; /** * @throws FormcycleConfigurationException @@ -25,50 +19,30 @@ final class FormcycleConfiguration public static function createFromExtensionConfiguration(array $extConfiguration): self { $config = new self(); - $config->formCycleUrl = rtrim($extConfiguration['formCycleUrl'] ?? '', '/'); + $config->formCycleUrl = rtrim($extConfiguration['formcycleUrl'] ?? '', '/'); if (!$config->formCycleUrl || !GeneralUtility::isValidUrl($config->formCycleUrl)) { throw new FormcycleConfigurationException( - 'Invalid formCycleUrl "' . $config->formCycleUrl . '"', + 'Invalid formcycleUrl "' . $config->formCycleUrl . '"', 1709041996 ); } - $config->formCycleUser = $extConfiguration['formCycleUser'] ?? ''; - if (!$config->formCycleUser) { - throw new FormcycleConfigurationException('No formCycleUser set', 1709052037); - } - - $config->formCyclePass = $extConfiguration['formCyclePass'] ?? ''; - if (!$config->formCyclePass) { - throw new FormcycleConfigurationException('No formCyclePass set', 1709538727); - } - - $config->formCycleClientId = $extConfiguration['formCycleClientId'] ?? ''; - if (!$config->formCycleClientId) { - throw new FormcycleConfigurationException('No formCycleClientId set', 1709538688); + $config->formcycleClientId = $extConfiguration['formcycleClientId'] ?? ''; + if (!$config->formcycleClientId) { + throw new FormcycleConfigurationException('No formcycleClientId set', 1709538688); } $config->integrationMode = IntegrationMode::tryFrom($extConfiguration['integrationMode'] ?? '') ?? IntegrationMode::Integrated; - $config->formCycleFrontendUrl = $extConfiguration['formCycleFrontendUrl'] ?? ''; - if ($config->formCycleFrontendUrl && !GeneralUtility::isValidUrl($config->formCycleFrontendUrl)) { - throw new FormcycleConfigurationException( - 'Invalid formCycleFrontendUrl "' . $config->formCycleFrontendUrl . '"', - 1709052152 - ); - } - return $config; } public function getFormListUrl(): string { return sprintf( - '%s/plugin?name=FormList&xfc-rp-username=%s&xfc-rp-password=%s&xfc-rp-client=%s&format=json', + '%s/plugin?name=FormListJson&xfc-rp-client=%s', $this->formCycleUrl, - $this->formCycleUser, - $this->formCyclePass, - $this->formCycleClientId, + $this->formcycleClientId, ); } @@ -79,7 +53,7 @@ public function getFormCycleUrl(): string public function getAdminUrl(): string { - return $this->formCycleFrontendUrl ?: $this->formCycleUrl; + return $this->formCycleUrl; } public function getIntegrationMode(): IntegrationMode diff --git a/Tests/Unit/Dto/FormcycleConfigurationTest.php b/Tests/Unit/Dto/FormcycleConfigurationTest.php index 8c83d1e..2842af7 100644 --- a/Tests/Unit/Dto/FormcycleConfigurationTest.php +++ b/Tests/Unit/Dto/FormcycleConfigurationTest.php @@ -11,11 +11,8 @@ class FormcycleConfigurationTest extends UnitTestCase { private array $validExtensionConfiguration = [ - 'formCycleUrl' => 'https://example.com', - 'formCycleFrontendUrl' => '', - 'formCycleUser' => 'username', - 'formCyclePass' => 'password', - 'formCycleClientId' => '123456', + 'formcycleUrl' => 'https://example.com', + 'formcycleClientId' => '123456', 'integrationMode' => '', ]; @@ -34,31 +31,7 @@ public function testInvalidFormcycleUrl(): void { $this->expectException(FormcycleConfigurationException::class); $this->expectExceptionCode(1709041996); - $this->validExtensionConfiguration['formCycleUrl'] = 'x'; - FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); - } - - public function testInvalidFormcycleFrontendUrl(): void - { - $this->expectException(FormcycleConfigurationException::class); - $this->expectExceptionCode(1709052152); - $this->validExtensionConfiguration['formCycleFrontendUrl'] = 'invalid url'; - FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); - } - - public function testMissingUsername(): void - { - $this->expectException(FormcycleConfigurationException::class); - $this->expectExceptionCode(1709052037); - $this->validExtensionConfiguration['formCycleUser'] = ''; - FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); - } - - public function testMissingPassword(): void - { - $this->expectException(FormcycleConfigurationException::class); - $this->expectExceptionCode(1709538727); - $this->validExtensionConfiguration['formCyclePass'] = ''; + $this->validExtensionConfiguration['formcycleUrl'] = 'x'; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } @@ -66,7 +39,7 @@ public function testMissingClientId(): void { $this->expectException(FormcycleConfigurationException::class); $this->expectExceptionCode(1709538688); - $this->validExtensionConfiguration['formCycleClientId'] = ''; + $this->validExtensionConfiguration['formcycleClientId'] = ''; FormcycleConfiguration::createFromExtensionConfiguration($this->validExtensionConfiguration); } diff --git a/ext_conf_template.txt b/ext_conf_template.txt index 05de4c8..8ea2717 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -4,14 +4,5 @@ formCycleUrl = # cat=basic//100; type=string; label=The client ID of your Formcycle installation formCycleClientId = -# cat=basic//100; type=string; label=Formcycle Frontend Server URL (optional) -formCycleFrontendUrl = - -# cat=basic//102; type=string; label=Formcycle login username -formCycleUser = - -# cat=basic//103; type=string; label=Formcycle login password -formCyclePass = - # cat=basic//104; type=options[integrated, AJAX (TYPO3), AJAX (FORMCYCLE), iFrame]; label=Integration mode -integrationMode = integrated +defaultIntegrationMode = integrated From 538ef19106114244ebb201cf8fd7b776e9366a83 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 5 Mar 2024 18:14:35 +0100 Subject: [PATCH 48/66] feat: add functional test for FormcycleService --- Classes/Service/FormcycleService.php | 2 +- .../Service/FormcycleServiceTest.php | 48 +++++++++++++++++++ Tests/Unit/Service/FormcycleServiceTest.php | 12 ----- composer.json | 1 + phpstan.neon | 11 ++--- phpunit.functional.xml | 39 +++++++++++++++ 6 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 Tests/Functional/Service/FormcycleServiceTest.php delete mode 100644 Tests/Unit/Service/FormcycleServiceTest.php create mode 100644 phpunit.functional.xml diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index adf873b..978cfd2 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -59,7 +59,7 @@ public function getAvailableFormConfigurationByFormId(string $formId): array public function getAvailableForms(): array { $forms = $this->cache->get('availableForms'); - if ($forms === false) { + if (!$forms) { $forms = $this->loadAvailableForms(); } diff --git a/Tests/Functional/Service/FormcycleServiceTest.php b/Tests/Functional/Service/FormcycleServiceTest.php new file mode 100644 index 0000000..1345ce2 --- /dev/null +++ b/Tests/Functional/Service/FormcycleServiceTest.php @@ -0,0 +1,48 @@ + [ + 'xm_formcycle' => [ + 'formcycleClientId' => '2252', + 'formcycleUrl' => 'https://pro.form.cloud/formcycle/', + 'defaultIntegrationMode' => 'integrated', + ], + ], + ]; + + protected array $testExtensionsToLoad = [ + 'typo3conf/ext/xm_formcycle', + ]; + + private FormcycleService $subject; + + public function setUp(): void + { + parent::setUp(); + + $extConf = $this->get(ExtensionConfiguration::class); + $cache = $this->createMock(FrontendInterface::class); + $uriBuilder = $this->get(UriBuilder::class); + + $this->subject = new FormcycleService($extConf, $cache, $uriBuilder); + } + + public function testLoadAvailableForms(): void + { + $forms = $this->subject->getAvailableForms(); + self::assertIsArray($forms); + self::assertGreaterThanOrEqual(3, count($forms), 'At least 3 test forms in response'); + self::assertArrayHasKey('form_id', $forms[0], 'Formcycle response: form_id is set'); + self::assertArrayHasKey('thumbnail', $forms[0], 'Formcycle response: thumbnail is set'); + } +} diff --git a/Tests/Unit/Service/FormcycleServiceTest.php b/Tests/Unit/Service/FormcycleServiceTest.php deleted file mode 100644 index 1716c21..0000000 --- a/Tests/Unit/Service/FormcycleServiceTest.php +++ /dev/null @@ -1,12 +0,0 @@ - + + + + + + + + + + + + + + + + + Tests/Functional + + + + + Classes + + + From 456b34b8784519839abea465ce371263801caf5e Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Tue, 5 Mar 2024 18:46:36 +0100 Subject: [PATCH 49/66] docs: update extension settings --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8c17961..d096484 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,8 @@ Set your Formcycle credentials in the extension configuration via TYPO3 backend ```php 'EXTENSIONS' => [ 'xm_formcycle' => [ - 'formCycleUrl' => 'https://pro.formcloud.de/', - 'formCycleClientId' => '4231', - 'formCycleUser' => 'user@examle.com', - 'formCyclePass' => 'the-password', - 'formCycleFrontendUrl' => '', // optional - 'integrationMode' => '', // optional + 'formcycleUrl' => 'https://pro.formcloud.de/', + 'formcycleClientId' => '4231', ], ] ``` From a020adfc5dbf3fcdac19df4489325782b320218a Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 6 Mar 2024 11:20:47 +0100 Subject: [PATCH 50/66] feat: update readme, rename sca workflow --- .github/workflows/{test-sca.yml => sca.yml} | 0 README.md | 8 ++------ 2 files changed, 2 insertions(+), 6 deletions(-) rename .github/workflows/{test-sca.yml => sca.yml} (100%) diff --git a/.github/workflows/test-sca.yml b/.github/workflows/sca.yml similarity index 100% rename from .github/workflows/test-sca.yml rename to .github/workflows/sca.yml diff --git a/README.md b/README.md index 8c17961..d096484 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,8 @@ Set your Formcycle credentials in the extension configuration via TYPO3 backend ```php 'EXTENSIONS' => [ 'xm_formcycle' => [ - 'formCycleUrl' => 'https://pro.formcloud.de/', - 'formCycleClientId' => '4231', - 'formCycleUser' => 'user@examle.com', - 'formCyclePass' => 'the-password', - 'formCycleFrontendUrl' => '', // optional - 'integrationMode' => '', // optional + 'formcycleUrl' => 'https://pro.formcloud.de/', + 'formcycleClientId' => '4231', ], ] ``` From f384ab64661522d016dd9b67bf117940aba3f98f Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Wed, 6 Mar 2024 16:31:21 +0100 Subject: [PATCH 51/66] feat: install codeception, add first backend acceptance test --- .../manifest.yaml | 9 + .ddev/config.yaml | 11 +- .ddev/docker-compose.selenium-chrome.yaml | 34 + .gitignore | 3 +- Tests/Acceptance/Backend/ElementCest.php | 37 + Tests/Acceptance/Fixtures/be_users.sql | 6 + Tests/Acceptance/Fixtures/pages.sql | 6 + Tests/Acceptance/Fixtures/sys_template.sql | 5 + Tests/Acceptance/Support/AcceptanceTester.php | 31 + .../BackendFormcycleEnvironment.php | 22 + .../Support/Helper/PageTreeHelper.php | 25 + .../_generated/AcceptanceTesterActions.php | 4964 +++++++++++++++++ codeception.yml | 58 + composer.json | 4 + phpunit.functional.xml | 10 +- phpunit.unit.xml | 6 +- 16 files changed, 5218 insertions(+), 13 deletions(-) create mode 100644 .ddev/addon-metadata/ddev-selenium-standalone-chrome/manifest.yaml create mode 100644 .ddev/docker-compose.selenium-chrome.yaml create mode 100644 Tests/Acceptance/Backend/ElementCest.php create mode 100644 Tests/Acceptance/Fixtures/be_users.sql create mode 100644 Tests/Acceptance/Fixtures/pages.sql create mode 100644 Tests/Acceptance/Fixtures/sys_template.sql create mode 100644 Tests/Acceptance/Support/AcceptanceTester.php create mode 100644 Tests/Acceptance/Support/Extensions/BackendFormcycleEnvironment.php create mode 100644 Tests/Acceptance/Support/Helper/PageTreeHelper.php create mode 100644 Tests/Acceptance/Support/_generated/AcceptanceTesterActions.php create mode 100644 codeception.yml diff --git a/.ddev/addon-metadata/ddev-selenium-standalone-chrome/manifest.yaml b/.ddev/addon-metadata/ddev-selenium-standalone-chrome/manifest.yaml new file mode 100644 index 0000000..9bbc07b --- /dev/null +++ b/.ddev/addon-metadata/ddev-selenium-standalone-chrome/manifest.yaml @@ -0,0 +1,9 @@ +name: ddev-selenium-standalone-chrome +repository: ddev/ddev-selenium-standalone-chrome +version: 1.0.4 +install_date: "2024-03-06T11:10:28+01:00" +project_files: + - docker-compose.selenium-chrome.yaml + - config.selenium-standalone-chrome.yaml +global_files: [] +removal_actions: [] diff --git a/.ddev/config.yaml b/.ddev/config.yaml index e8ca457..d519486 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -13,9 +13,18 @@ database: version: "10.4" use_dns_when_possible: true composer_version: "2" -web_environment: [] webimage_extra_packages: [libxml2-utils] +omit_containers: [ddev-ssh-agent] nodejs_version: "20" +disable_settings_management: true +web_environment: + - TESTING_DOMAIN=$DDEV_HOSTNAME + - XDEBUG_MODE=coverage + - TYPO3_CONTEXT=Development/DDEV + - typo3DatabaseHost=db + - typo3DatabaseUsername=root + - typo3DatabasePassword=root + - typo3DatabaseName=db # Key features of DDEV's config.yaml: diff --git a/.ddev/docker-compose.selenium-chrome.yaml b/.ddev/docker-compose.selenium-chrome.yaml new file mode 100644 index 0000000..c126da8 --- /dev/null +++ b/.ddev/docker-compose.selenium-chrome.yaml @@ -0,0 +1,34 @@ +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev get ddev/ddev-selenium-standalone-chrome +# +# This file comes from https://github.com/ddev/ddev-selenium-standalone-chrome +# +version: '3.6' +services: + selenium-chrome: + image: seleniarm/standalone-chromium:4.1.4-20220429 + container_name: ddev-${DDEV_SITENAME}-selenium-chrome + expose: + # The internal noVNC port, which operates over HTTP so it can be exposed + # through the router. + - 7900 + environment: + - VIRTUAL_HOST=$DDEV_HOSTNAME + - HTTPS_EXPOSE=7900:7900 + - HTTP_EXPOSE=7910:7900 + external_links: + - ddev-router:${DDEV_SITENAME}.${DDEV_TLD} + # To enable VNC access for traditional VNC clients like macOS "Screen Sharing", + # uncomment the following two lines. + #ports: + # - "5900:5900" + labels: + com.ddev.site-name: ${DDEV_SITENAME} + com.ddev.approot: $DDEV_APPROOT + volumes: + - ".:/mnt/ddev_config" + + web: + links: + - selenium-chrome diff --git a/.gitignore b/.gitignore index 8c83d9e..e23d886 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -.Build .idea +coverage public/.htaccess public/_assets public/fileadmin @@ -12,3 +12,4 @@ var vendor/ composer.lock package.lock +Tests/Acceptance/_output diff --git a/Tests/Acceptance/Backend/ElementCest.php b/Tests/Acceptance/Backend/ElementCest.php new file mode 100644 index 0000000..d4cf9cd --- /dev/null +++ b/Tests/Acceptance/Backend/ElementCest.php @@ -0,0 +1,37 @@ +amOnPage('/typo3/'); + $I->waitForElementVisible('input[name="username"]'); + $I->waitForElementVisible('input[type="password"]'); + $I->fillField('input[name="username"]', 'admin'); + $I->fillField('input[type="password"]', 'changeme'); + $I->click('button[type="submit"]'); + $I->waitForElementNotVisible('form[name="loginform"]'); + $I->seeCookie('be_typo_user'); + } + + // tests + public function tryToTest(AcceptanceTester $I, PageTreeHelper $pageTree) + { + $I->click('Page'); + + $I->waitForElementVisible(PageTreeHelper::$pageTreeFrameSelector); + + $pageTree->clickElement('Main'); + + $I->switchToContentFrame(); + + $I->click('typo3-backend-new-content-element-wizard-button'); + + $I->makeScreenshot(); + } +} diff --git a/Tests/Acceptance/Fixtures/be_users.sql b/Tests/Acceptance/Fixtures/be_users.sql new file mode 100644 index 0000000..ee820ea --- /dev/null +++ b/Tests/Acceptance/Fixtures/be_users.sql @@ -0,0 +1,6 @@ +delete +from `be_users`; + +insert into `be_users` (`uid`, `pid`, `username`, `admin`, `password`) +values (1, 0, 'admin', '1', + '$argon2i$v=19$m=65536,t=16,p=1$YXZ4ZkExRmV0L0FtUFlHWg$8kOmOsOiNhRcWhtpyo/8e7Fzyk98SlVKvgoM798nmK8'); diff --git a/Tests/Acceptance/Fixtures/pages.sql b/Tests/Acceptance/Fixtures/pages.sql new file mode 100644 index 0000000..088791d --- /dev/null +++ b/Tests/Acceptance/Fixtures/pages.sql @@ -0,0 +1,6 @@ +delete +from `pages`; + +insert into `pages` (`uid`, `pid`, `title`, `slug`, `sys_language_uid`, `l10n_parent`, `l10n_source`, `perms_userid`, + `perms_groupid`, `perms_user`, `perms_group`, `perms_everybody`, `doktype`, `is_siteroot`) +values (1, 0, 'Main', '/', 0, 0, 0, 1, 1, 31, 31, 1, 1, 1); diff --git a/Tests/Acceptance/Fixtures/sys_template.sql b/Tests/Acceptance/Fixtures/sys_template.sql new file mode 100644 index 0000000..8a3da36 --- /dev/null +++ b/Tests/Acceptance/Fixtures/sys_template.sql @@ -0,0 +1,5 @@ +delete +from `sys_template`; + +insert into `sys_template` (`pid`, `title`, `root`, `clear`, `include_static_file`) +values (1, 'Home', 1, 3, 'EXT:bootstrap_package/Configuration/TypoScript,EXT:xm_formcycle/Configuration/TypoScript'); diff --git a/Tests/Acceptance/Support/AcceptanceTester.php b/Tests/Acceptance/Support/AcceptanceTester.php new file mode 100644 index 0000000..d52f128 --- /dev/null +++ b/Tests/Acceptance/Support/AcceptanceTester.php @@ -0,0 +1,31 @@ + [ + 'core', + 'extbase', + 'fluid', + 'backend', + 'install', + 'frontend', + ], + 'testExtensionsToLoad' => [ + 'typo3conf/ext/xm_formcycle', + ], + ]; +} diff --git a/Tests/Acceptance/Support/Helper/PageTreeHelper.php b/Tests/Acceptance/Support/Helper/PageTreeHelper.php new file mode 100644 index 0000000..dc111d1 --- /dev/null +++ b/Tests/Acceptance/Support/Helper/PageTreeHelper.php @@ -0,0 +1,25 @@ +tester = $tester; + } + + public function clickElement(string $name): void + { + $context = $this->getPageTreeElement(); + $context->findElement(\Facebook\WebDriver\WebDriverBy::xpath('//*[text()="Main"]'))->click(); + } +} diff --git a/Tests/Acceptance/Support/_generated/AcceptanceTesterActions.php b/Tests/Acceptance/Support/_generated/AcceptanceTesterActions.php new file mode 100644 index 0000000..29a764f --- /dev/null +++ b/Tests/Acceptance/Support/_generated/AcceptanceTesterActions.php @@ -0,0 +1,4964 @@ +getScenario()->runStep(new \Codeception\Step\Action('debugWebDriverLogs', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Changes the subdomain for the 'url' configuration parameter. + * Does not open a page; use `amOnPage` for that. + * + * ``` php + * amOnSubdomain('user'); + * $I->amOnPage('/'); + * // moves to https://user.mysite.com/ + * ``` + * + * @see \Codeception\Module\WebDriver::amOnSubdomain() + */ + public function amOnSubdomain(string $subdomain): void { + $this->getScenario()->runStep(new \Codeception\Step\Condition('amOnSubdomain', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Takes a screenshot of the current window and saves it to `tests/_output/debug`. + * + * ``` php + * amOnPage('/user/edit'); + * $I->makeScreenshot('edit_page'); + * // saved to: tests/_output/debug/edit_page.png + * $I->makeScreenshot(); + * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png + * ``` + * @see \Codeception\Module\WebDriver::makeScreenshot() + */ + public function makeScreenshot(?string $name = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('makeScreenshot', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Takes a screenshot of an element of the current window and saves it to `tests/_output/debug`. + * + * ``` php + * amOnPage('/user/edit'); + * $I->makeElementScreenshot('#dialog', 'edit_page'); + * // saved to: tests/_output/debug/edit_page.png + * $I->makeElementScreenshot('#dialog'); + * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png + * ``` + * + * @param WebDriverBy|array $selector + * @see \Codeception\Module\WebDriver::makeElementScreenshot() + */ + public function makeElementScreenshot($selector, ?string $name = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('makeElementScreenshot', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Use this method within an [interactive pause](https://codeception.com/docs/02-GettingStarted#Interactive-Pause) to save the HTML source code of the current page. + * + * ```php + * makeHtmlSnapshot('edit_page'); + * // saved to: tests/_output/debug/edit_page.html + * $I->makeHtmlSnapshot(); + * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.html + * ``` + * @see \Codeception\Module\WebDriver::makeHtmlSnapshot() + */ + public function makeHtmlSnapshot(?string $name = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('makeHtmlSnapshot', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Resize the current window. + * + * ``` php + * resizeWindow(800, 600); + * + * ``` + * @see \Codeception\Module\WebDriver::resizeWindow() + */ + public function resizeWindow(int $width, int $height): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('resizeWindow', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that a cookie with the given name is set. + * You can set additional cookie params like `domain`, `path` as array passed in last argument. + * + * ```php + * seeCookie('PHPSESSID'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::seeCookie() + */ + public function seeCookie($cookie, array $params = [], bool $showDebug = true): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCookie', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that a cookie with the given name is set. + * You can set additional cookie params like `domain`, `path` as array passed in last argument. + * + * ```php + * seeCookie('PHPSESSID'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::seeCookie() + */ + public function canSeeCookie($cookie, array $params = [], bool $showDebug = true): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCookie', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that there isn't a cookie with the given name. + * You can set additional cookie params like `domain`, `path` as array passed in last argument. + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::dontSeeCookie() + */ + public function dontSeeCookie($cookie, array $params = [], bool $showDebug = true): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeCookie', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that there isn't a cookie with the given name. + * You can set additional cookie params like `domain`, `path` as array passed in last argument. + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::dontSeeCookie() + */ + public function cantSeeCookie($cookie, array $params = [], bool $showDebug = true): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCookie', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Sets a cookie with the given name and value. + * You can set additional cookie params like `domain`, `path`, `expires`, `secure` in array passed as last argument. + * + * ```php + * setCookie('PHPSESSID', 'el4ukv0kqbvoirg7nkp4dncpk3'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::setCookie() + */ + public function setCookie($name, $value, array $params = [], $showDebug = true): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('setCookie', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Unsets cookie with the given name. + * You can set additional cookie params like `domain`, `path` in array passed as last argument. + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::resetCookie() + */ + public function resetCookie($cookie, array $params = [], bool $showDebug = true): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('resetCookie', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Grabs a cookie value. + * You can set additional cookie params like `domain`, `path` in array passed as last argument. + * If the cookie is set by an ajax request (XMLHttpRequest), there might be some delay caused by the browser, so try `$I->wait(0.1)`. + * @see \Codeception\Module\WebDriver::grabCookie() + */ + public function grabCookie($cookie, array $params = []): mixed { + return $this->getScenario()->runStep(new \Codeception\Step\Action('grabCookie', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Grabs current page source code. + * + * @throws ModuleException if no page was opened. + * @return string Current page source code. + * @see \Codeception\Module\WebDriver::grabPageSource() + */ + public function grabPageSource(): string { + return $this->getScenario()->runStep(new \Codeception\Step\Action('grabPageSource', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Open web page at the given absolute URL and sets its hostname as the base host. + * + * ``` php + * amOnUrl('https://codeception.com'); + * $I->amOnPage('/quickstart'); // moves to https://codeception.com/quickstart + * ``` + * @see \Codeception\Module\WebDriver::amOnUrl() + */ + public function amOnUrl($url): void { + $this->getScenario()->runStep(new \Codeception\Step\Condition('amOnUrl', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Opens the page for the given relative URI. + * + * ```php + * amOnPage('/'); + * // opens /register page + * $I->amOnPage('/register'); + * ``` + * @see \Codeception\Module\WebDriver::amOnPage() + */ + public function amOnPage($page): void { + $this->getScenario()->runStep(new \Codeception\Step\Condition('amOnPage', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current page contains the given string (case insensitive). + * + * You can specify a specific HTML element (via CSS or XPath) as the second + * parameter to only search within that element. + * + * ```php + * see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page + * $I->see('Sign Up', '//body/h1'); // with XPath + * $I->see('Sign Up', ['css' => 'body h1']); // with strict CSS locator + * ``` + * + * Note that the search is done after stripping all HTML tags from the body, + * so `$I->see('strong')` will return true for strings like: + * + * - `

      I am Stronger than thou

      ` + * - `` + * + * But will *not* be true for strings like: + * + * - `Home` + * - `
      Home` + * - `` + * + * For checking the raw source code, use `seeInSource()`. + * + * @param array|string $selector optional + * @see \Codeception\Module\WebDriver::see() + */ + public function see($text, $selector = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('see', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current page contains the given string (case insensitive). + * + * You can specify a specific HTML element (via CSS or XPath) as the second + * parameter to only search within that element. + * + * ```php + * see('Logout'); // I can suppose user is logged in + * $I->see('Sign Up', 'h1'); // I can suppose it's a signup page + * $I->see('Sign Up', '//body/h1'); // with XPath + * $I->see('Sign Up', ['css' => 'body h1']); // with strict CSS locator + * ``` + * + * Note that the search is done after stripping all HTML tags from the body, + * so `$I->see('strong')` will return true for strings like: + * + * - `

      I am Stronger than thou

      ` + * - `` + * + * But will *not* be true for strings like: + * + * - `Home` + * - `
      Home` + * - `` + * + * For checking the raw source code, use `seeInSource()`. + * + * @param array|string $selector optional + * @see \Codeception\Module\WebDriver::see() + */ + public function canSee($text, $selector = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('see', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current page doesn't contain the text specified (case insensitive). + * Give a locator as the second parameter to match a specific region. + * + * ```php + * dontSee('Login'); // I can suppose user is already logged in + * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page + * $I->dontSee('Sign Up','//body/h1'); // with XPath + * $I->dontSee('Sign Up', ['css' => 'body h1']); // with strict CSS locator + * ``` + * + * Note that the search is done after stripping all HTML tags from the body, + * so `$I->dontSee('strong')` will fail on strings like: + * + * - `

      I am Stronger than thou

      ` + * - `` + * + * But will ignore strings like: + * + * - `Home` + * - `
      Home` + * - `` + * + * For checking the raw source code, use `seeInSource()`. + * + * @param array|string $selector optional + * @see \Codeception\Module\WebDriver::dontSee() + */ + public function dontSee($text, $selector = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSee', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current page doesn't contain the text specified (case insensitive). + * Give a locator as the second parameter to match a specific region. + * + * ```php + * dontSee('Login'); // I can suppose user is already logged in + * $I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page + * $I->dontSee('Sign Up','//body/h1'); // with XPath + * $I->dontSee('Sign Up', ['css' => 'body h1']); // with strict CSS locator + * ``` + * + * Note that the search is done after stripping all HTML tags from the body, + * so `$I->dontSee('strong')` will fail on strings like: + * + * - `

      I am Stronger than thou

      ` + * - `` + * + * But will ignore strings like: + * + * - `Home` + * - `
      Home` + * - `` + * + * For checking the raw source code, use `seeInSource()`. + * + * @param array|string $selector optional + * @see \Codeception\Module\WebDriver::dontSee() + */ + public function cantSee($text, $selector = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSee', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current page contains the given string in its + * raw source code. + * + * ```php + * seeInSource('

      Green eggs & ham

      '); + * ``` + * @see \Codeception\Module\WebDriver::seeInSource() + */ + public function seeInSource($raw): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInSource', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current page contains the given string in its + * raw source code. + * + * ```php + * seeInSource('

      Green eggs & ham

      '); + * ``` + * @see \Codeception\Module\WebDriver::seeInSource() + */ + public function canSeeInSource($raw): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInSource', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current page contains the given string in its + * raw source code. + * + * ```php + * dontSeeInSource('

      Green eggs & ham

      '); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeInSource() + */ + public function dontSeeInSource($raw): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInSource', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current page contains the given string in its + * raw source code. + * + * ```php + * dontSeeInSource('

      Green eggs & ham

      '); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeInSource() + */ + public function cantSeeInSource($raw): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInSource', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the page source contains the given string. + * + * ```php + * seeInPageSource('getScenario()->runStep(new \Codeception\Step\Assertion('seeInPageSource', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the page source contains the given string. + * + * ```php + * seeInPageSource('getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInPageSource', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the page source doesn't contain the given string. + * @see \Codeception\Module\WebDriver::dontSeeInPageSource() + */ + public function dontSeeInPageSource(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInPageSource', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the page source doesn't contain the given string. + * @see \Codeception\Module\WebDriver::dontSeeInPageSource() + */ + public function cantSeeInPageSource(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInPageSource', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Perform a click on a link or a button, given by a locator. + * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string. + * For buttons, the "value" attribute, "name" attribute, and inner text are searched. + * For links, the link text is searched. + * For images, the "alt" attribute and inner text of any parent links are searched. + * + * The second parameter is a context (CSS or XPath locator) to narrow the search. + * + * Note that if the locator matches a button of type `submit`, the form will be submitted. + * + * ```php + * click('Logout'); + * // button of form + * $I->click('Submit'); + * // CSS button + * $I->click('#form input[type=submit]'); + * // XPath + * $I->click('//form/*[@type="submit"]'); + * // link in context + * $I->click('Logout', '#nav'); + * // using strict locator + * $I->click(['link' => 'Login']); + * ``` + * @param string|array $link + * @see \Codeception\Module\WebDriver::click() + */ + public function click($link, $context = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('click', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that there's a link with the specified text. + * Give a full URL as the second parameter to match links with that exact URL. + * + * ```php + * seeLink('Logout'); // matches Logout + * $I->seeLink('Logout','/logout'); // matches
      Logout + * ``` + * @see \Codeception\Module\WebDriver::seeLink() + */ + public function seeLink(string $text, ?string $url = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeLink', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that there's a link with the specified text. + * Give a full URL as the second parameter to match links with that exact URL. + * + * ```php + * seeLink('Logout'); // matches Logout + * $I->seeLink('Logout','/logout'); // matches Logout + * ``` + * @see \Codeception\Module\WebDriver::seeLink() + */ + public function canSeeLink(string $text, ?string $url = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeLink', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the page doesn't contain a link with the given string. + * If the second parameter is given, only links with a matching "href" attribute will be checked. + * + * ```php + * dontSeeLink('Logout'); // I suppose user is not logged in + * $I->dontSeeLink('Checkout now', '/store/cart.php'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeLink() + */ + public function dontSeeLink(string $text, string $url = ""): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeLink', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the page doesn't contain a link with the given string. + * If the second parameter is given, only links with a matching "href" attribute will be checked. + * + * ```php + * dontSeeLink('Logout'); // I suppose user is not logged in + * $I->dontSeeLink('Checkout now', '/store/cart.php'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeLink() + */ + public function cantSeeLink(string $text, string $url = ""): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeLink', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that current URI contains the given string. + * + * ```php + * seeInCurrentUrl('home'); + * // to match: /users/1 + * $I->seeInCurrentUrl('/users/'); + * ``` + * @see \Codeception\Module\WebDriver::seeInCurrentUrl() + */ + public function seeInCurrentUrl(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInCurrentUrl', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that current URI contains the given string. + * + * ```php + * seeInCurrentUrl('home'); + * // to match: /users/1 + * $I->seeInCurrentUrl('/users/'); + * ``` + * @see \Codeception\Module\WebDriver::seeInCurrentUrl() + */ + public function canSeeInCurrentUrl(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInCurrentUrl', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current URL is equal to the given string. + * Unlike `seeInCurrentUrl`, this only matches the full URL. + * + * ```php + * seeCurrentUrlEquals('/'); + * ``` + * @see \Codeception\Module\WebDriver::seeCurrentUrlEquals() + */ + public function seeCurrentUrlEquals(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCurrentUrlEquals', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current URL is equal to the given string. + * Unlike `seeInCurrentUrl`, this only matches the full URL. + * + * ```php + * seeCurrentUrlEquals('/'); + * ``` + * @see \Codeception\Module\WebDriver::seeCurrentUrlEquals() + */ + public function canSeeCurrentUrlEquals(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlEquals', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current URL matches the given regular expression. + * + * ```php + * seeCurrentUrlMatches('~^/users/(\d+)~'); + * ``` + * @see \Codeception\Module\WebDriver::seeCurrentUrlMatches() + */ + public function seeCurrentUrlMatches(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCurrentUrlMatches', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current URL matches the given regular expression. + * + * ```php + * seeCurrentUrlMatches('~^/users/(\d+)~'); + * ``` + * @see \Codeception\Module\WebDriver::seeCurrentUrlMatches() + */ + public function canSeeCurrentUrlMatches(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCurrentUrlMatches', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current URI doesn't contain the given string. + * + * ```php + * dontSeeInCurrentUrl('/users/'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeInCurrentUrl() + */ + public function dontSeeInCurrentUrl(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInCurrentUrl', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current URI doesn't contain the given string. + * + * ```php + * dontSeeInCurrentUrl('/users/'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeInCurrentUrl() + */ + public function cantSeeInCurrentUrl(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInCurrentUrl', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the current URL doesn't equal the given string. + * Unlike `dontSeeInCurrentUrl`, this only matches the full URL. + * + * ```php + * dontSeeCurrentUrlEquals('/'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeCurrentUrlEquals() + */ + public function dontSeeCurrentUrlEquals(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeCurrentUrlEquals', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the current URL doesn't equal the given string. + * Unlike `dontSeeInCurrentUrl`, this only matches the full URL. + * + * ```php + * dontSeeCurrentUrlEquals('/'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeCurrentUrlEquals() + */ + public function cantSeeCurrentUrlEquals(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlEquals', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that current url doesn't match the given regular expression. + * + * ```php + * dontSeeCurrentUrlMatches('~^/users/(\d+)~'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeCurrentUrlMatches() + */ + public function dontSeeCurrentUrlMatches(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeCurrentUrlMatches', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that current url doesn't match the given regular expression. + * + * ```php + * dontSeeCurrentUrlMatches('~^/users/(\d+)~'); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeCurrentUrlMatches() + */ + public function cantSeeCurrentUrlMatches(string $uri): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCurrentUrlMatches', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Executes the given regular expression against the current URI and returns the first capturing group. + * If no parameters are provided, the full URI is returned. + * + * ```php + * grabFromCurrentUrl('~^/user/(\d+)/~'); + * $uri = $I->grabFromCurrentUrl(); + * ``` + * @see \Codeception\Module\WebDriver::grabFromCurrentUrl() + */ + public function grabFromCurrentUrl($uri = NULL): mixed { + return $this->getScenario()->runStep(new \Codeception\Step\Action('grabFromCurrentUrl', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the specified checkbox is checked. + * + * ```php + * seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. + * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); + * ``` + * @see \Codeception\Module\WebDriver::seeCheckboxIsChecked() + */ + public function seeCheckboxIsChecked($checkbox): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeCheckboxIsChecked', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the specified checkbox is checked. + * + * ```php + * seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form. + * $I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]'); + * ``` + * @see \Codeception\Module\WebDriver::seeCheckboxIsChecked() + */ + public function canSeeCheckboxIsChecked($checkbox): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeCheckboxIsChecked', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Check that the specified checkbox is unchecked. + * + * ```php + * dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. + * ``` + * @see \Codeception\Module\WebDriver::dontSeeCheckboxIsChecked() + */ + public function dontSeeCheckboxIsChecked($checkbox): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeCheckboxIsChecked', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Check that the specified checkbox is unchecked. + * + * ```php + * dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms + * $I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form. + * ``` + * @see \Codeception\Module\WebDriver::dontSeeCheckboxIsChecked() + */ + public function cantSeeCheckboxIsChecked($checkbox): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeCheckboxIsChecked', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the given input field or textarea *equals* (i.e. not just contains) the given value. + * Fields are matched by label text, the "name" attribute, CSS, or XPath. + * + * ```php + * seeInField('Body','Type your comment here'); + * $I->seeInField('form textarea[name=body]','Type your comment here'); + * $I->seeInField('form input[type=hidden]','hidden_value'); + * $I->seeInField('#searchform input','Search'); + * $I->seeInField('//form/*[@name=search]','Search'); + * $I->seeInField(['name' => 'search'], 'Search'); + * ``` + * + * @param string|array $field + * @see \Codeception\Module\WebDriver::seeInField() + */ + public function seeInField($field, $value): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInField', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the given input field or textarea *equals* (i.e. not just contains) the given value. + * Fields are matched by label text, the "name" attribute, CSS, or XPath. + * + * ```php + * seeInField('Body','Type your comment here'); + * $I->seeInField('form textarea[name=body]','Type your comment here'); + * $I->seeInField('form input[type=hidden]','hidden_value'); + * $I->seeInField('#searchform input','Search'); + * $I->seeInField('//form/*[@name=search]','Search'); + * $I->seeInField(['name' => 'search'], 'Search'); + * ``` + * + * @param string|array $field + * @see \Codeception\Module\WebDriver::seeInField() + */ + public function canSeeInField($field, $value): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInField', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that an input field or textarea doesn't contain the given value. + * For fuzzy locators, the field is matched by label text, CSS and XPath. + * + * ```php + * dontSeeInField('Body','Type your comment here'); + * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); + * $I->dontSeeInField('form input[type=hidden]','hidden_value'); + * $I->dontSeeInField('#searchform input','Search'); + * $I->dontSeeInField('//form/*[@name=search]','Search'); + * $I->dontSeeInField(['name' => 'search'], 'Search'); + * ``` + * @param string|array $field + * @see \Codeception\Module\WebDriver::dontSeeInField() + */ + public function dontSeeInField($field, $value): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInField', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that an input field or textarea doesn't contain the given value. + * For fuzzy locators, the field is matched by label text, CSS and XPath. + * + * ```php + * dontSeeInField('Body','Type your comment here'); + * $I->dontSeeInField('form textarea[name=body]','Type your comment here'); + * $I->dontSeeInField('form input[type=hidden]','hidden_value'); + * $I->dontSeeInField('#searchform input','Search'); + * $I->dontSeeInField('//form/*[@name=search]','Search'); + * $I->dontSeeInField(['name' => 'search'], 'Search'); + * ``` + * @param string|array $field + * @see \Codeception\Module\WebDriver::dontSeeInField() + */ + public function cantSeeInField($field, $value): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInField', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks if the array of form parameters (name => value) are set on the form matched with the + * passed selector. + * + * ```php + * seeInFormFields('form[name=myform]', [ + * 'input1' => 'value', + * 'input2' => 'other value', + * ]); + * ``` + * + * For multi-select elements, or to check values of multiple elements with the same name, an + * array may be passed: + * + * ```php + * seeInFormFields('.form-class', [ + * 'multiselect' => [ + * 'value1', + * 'value2', + * ], + * 'checkbox[]' => [ + * 'a checked value', + * 'another checked value', + * ], + * ]); + * ``` + * + * Additionally, checkbox values can be checked with a boolean. + * + * ```php + * seeInFormFields('#form-id', [ + * 'checkbox1' => true, // passes if checked + * 'checkbox2' => false, // passes if unchecked + * ]); + * ``` + * + * Pair this with submitForm for quick testing magic. + * + * ```php + * 'value', + * 'field2' => 'another value', + * 'checkbox1' => true, + * // ... + * ]; + * $I->submitForm('//form[@id=my-form]', string $form, 'submitButton'); + * // $I->amOnPage('/path/to/form-page') may be needed + * $I->seeInFormFields('//form[@id=my-form]', string $form); + * ``` + * @see \Codeception\Module\WebDriver::seeInFormFields() + */ + public function seeInFormFields($formSelector, array $params): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInFormFields', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks if the array of form parameters (name => value) are set on the form matched with the + * passed selector. + * + * ```php + * seeInFormFields('form[name=myform]', [ + * 'input1' => 'value', + * 'input2' => 'other value', + * ]); + * ``` + * + * For multi-select elements, or to check values of multiple elements with the same name, an + * array may be passed: + * + * ```php + * seeInFormFields('.form-class', [ + * 'multiselect' => [ + * 'value1', + * 'value2', + * ], + * 'checkbox[]' => [ + * 'a checked value', + * 'another checked value', + * ], + * ]); + * ``` + * + * Additionally, checkbox values can be checked with a boolean. + * + * ```php + * seeInFormFields('#form-id', [ + * 'checkbox1' => true, // passes if checked + * 'checkbox2' => false, // passes if unchecked + * ]); + * ``` + * + * Pair this with submitForm for quick testing magic. + * + * ```php + * 'value', + * 'field2' => 'another value', + * 'checkbox1' => true, + * // ... + * ]; + * $I->submitForm('//form[@id=my-form]', string $form, 'submitButton'); + * // $I->amOnPage('/path/to/form-page') may be needed + * $I->seeInFormFields('//form[@id=my-form]', string $form); + * ``` + * @see \Codeception\Module\WebDriver::seeInFormFields() + */ + public function canSeeInFormFields($formSelector, array $params): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInFormFields', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks if the array of form parameters (name => value) are not set on the form matched with + * the passed selector. + * + * ```php + * dontSeeInFormFields('form[name=myform]', [ + * 'input1' => 'non-existent value', + * 'input2' => 'other non-existent value', + * ]); + * ``` + * + * To check that an element hasn't been assigned any one of many values, an array can be passed + * as the value: + * + * ```php + * dontSeeInFormFields('.form-class', [ + * 'fieldName' => [ + * 'This value shouldn\'t be set', + * 'And this value shouldn\'t be set', + * ], + * ]); + * ``` + * + * Additionally, checkbox values can be checked with a boolean. + * + * ```php + * dontSeeInFormFields('#form-id', [ + * 'checkbox1' => true, // fails if checked + * 'checkbox2' => false, // fails if unchecked + * ]); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeInFormFields() + */ + public function dontSeeInFormFields($formSelector, array $params): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInFormFields', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks if the array of form parameters (name => value) are not set on the form matched with + * the passed selector. + * + * ```php + * dontSeeInFormFields('form[name=myform]', [ + * 'input1' => 'non-existent value', + * 'input2' => 'other non-existent value', + * ]); + * ``` + * + * To check that an element hasn't been assigned any one of many values, an array can be passed + * as the value: + * + * ```php + * dontSeeInFormFields('.form-class', [ + * 'fieldName' => [ + * 'This value shouldn\'t be set', + * 'And this value shouldn\'t be set', + * ], + * ]); + * ``` + * + * Additionally, checkbox values can be checked with a boolean. + * + * ```php + * dontSeeInFormFields('#form-id', [ + * 'checkbox1' => true, // fails if checked + * 'checkbox2' => false, // fails if unchecked + * ]); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeInFormFields() + */ + public function cantSeeInFormFields($formSelector, array $params): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInFormFields', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Selects an option in a select tag or in radio button group. + * + * ```php + * selectOption('form select[name=account]', 'Premium'); + * $I->selectOption('form input[name=payment]', 'Monthly'); + * $I->selectOption('//form/select[@name=account]', 'Monthly'); + * ``` + * + * Provide an array for the second argument to select multiple options: + * + * ```php + * selectOption('Which OS do you use?', ['Windows', 'Linux']); + * ``` + * + * Or provide an associative array for the second argument to specifically define which selection method should be used: + * + * ```php + * selectOption('Which OS do you use?', ['text' => 'Windows']); // Only search by text 'Windows' + * $I->selectOption('Which OS do you use?', ['value' => 'windows']); // Only search by value 'windows' + * ``` + * @see \Codeception\Module\WebDriver::selectOption() + */ + public function selectOption($select, $option): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('selectOption', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Unselect an option in the given select box. + * + * @param string|array|WebDriverBy $select + * @param string|array|WebDriverBy $option + * @see \Codeception\Module\WebDriver::unselectOption() + */ + public function unselectOption($select, $option): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('unselectOption', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Ticks a checkbox. For radio buttons, use the `selectOption` method instead. + * + * ```php + * checkOption('#agree'); + * ``` + * @see \Codeception\Module\WebDriver::checkOption() + */ + public function checkOption($option): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('checkOption', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Unticks a checkbox. + * + * ```php + * uncheckOption('#notify'); + * ``` + * @see \Codeception\Module\WebDriver::uncheckOption() + */ + public function uncheckOption($option): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('uncheckOption', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Fills a text field or textarea with the given string. + * + * ```php + * fillField("//input[@type='text']", "Hello World!"); + * $I->fillField(['name' => 'email'], 'jon@example.com'); + * ``` + * @see \Codeception\Module\WebDriver::fillField() + */ + public function fillField($field, $value): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('fillField', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Clears given field which isn't empty. + * + * ``` php + * clearField('#username'); + * ``` + * + * @param string|array|WebDriverBy $field + * @see \Codeception\Module\WebDriver::clearField() + */ + public function clearField($field): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('clearField', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Type in characters on active element. + * With a second parameter you can specify delay between key presses. + * + * ```php + * click('#input'); + * + * // type text in active element + * $I->type('Hello world'); + * + * // type text with a 1sec delay between chars + * $I->type('Hello World', 1); + * ``` + * + * This might be useful when you an input reacts to typing and you need to slow it down to emulate human behavior. + * For instance, this is how Credit Card fields can be filled in. + * + * @param int $delay [sec] + * @see \Codeception\Module\WebDriver::type() + */ + public function type(string $text, int $delay = 0): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('type', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Attaches a file relative to the Codeception `_data` directory to the given file upload field. + * + * ```php + * attachFile('input[@type="file"]', 'prices.xls'); + * ``` + * @see \Codeception\Module\WebDriver::attachFile() + */ + public function attachFile($field, string $filename): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('attachFile', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Finds and returns the text contents of the given element. + * If a fuzzy locator is used, the element is found using CSS, XPath, + * and by matching the full page source by regular expression. + * + * ```php + * grabTextFrom('h1'); + * $heading = $I->grabTextFrom('descendant-or-self::h1'); + * $value = $I->grabTextFrom('~getScenario()->runStep(new \Codeception\Step\Action('grabTextFrom', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Returns the value of the given attribute value from the given HTML element. For some attributes, the string `true` is returned instead of their literal value (e.g. `disabled="disabled"` or `required="required"`). + * Fails if the element is not found. Returns `null` if the attribute is not present on the element. + * + * ```php + * grabAttributeFrom('#tooltip', 'title'); + * ``` + * @see \Codeception\Module\WebDriver::grabAttributeFrom() + */ + public function grabAttributeFrom($cssOrXpath, $attribute): ?string { + return $this->getScenario()->runStep(new \Codeception\Step\Action('grabAttributeFrom', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Finds the value for the given form field. + * If a fuzzy locator is used, the field is found by field name, CSS, and XPath. + * + * ```php + * grabValueFrom('Name'); + * $name = $I->grabValueFrom('input[name=username]'); + * $name = $I->grabValueFrom('descendant-or-self::form/descendant::input[@name = 'username']'); + * $name = $I->grabValueFrom(['name' => 'username']); + * ``` + * @see \Codeception\Module\WebDriver::grabValueFrom() + */ + public function grabValueFrom($field): ?string { + return $this->getScenario()->runStep(new \Codeception\Step\Action('grabValueFrom', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Grabs either the text content, or attribute values, of nodes + * matched by $cssOrXpath and returns them as an array. + * + * ```html + * First + * Second + * Third + * ``` + * + * ```php + * grabMultiple('a'); + * + * // would return ['#first', '#second', '#third'] + * $aLinks = $I->grabMultiple('a', 'href'); + * ``` + * + * @return string[] + * @see \Codeception\Module\WebDriver::grabMultiple() + */ + public function grabMultiple($cssOrXpath, $attribute = NULL): array { + return $this->getScenario()->runStep(new \Codeception\Step\Action('grabMultiple', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the given element exists on the page and is visible. + * You can also specify expected attributes of this element. + * Only works if `` tag is present. + * + * ```php + * seeElement('.error'); + * $I->seeElement('//form/input[1]'); + * $I->seeElement('input', ['name' => 'login']); + * $I->seeElement('input', ['value' => '123456']); + * + * // strict locator in first arg, attributes in second + * $I->seeElement(['css' => 'form input'], ['name' => 'login']); + * ``` + * @see \Codeception\Module\WebDriver::seeElement() + */ + public function seeElement($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeElement', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the given element exists on the page and is visible. + * You can also specify expected attributes of this element. + * Only works if `` tag is present. + * + * ```php + * seeElement('.error'); + * $I->seeElement('//form/input[1]'); + * $I->seeElement('input', ['name' => 'login']); + * $I->seeElement('input', ['value' => '123456']); + * + * // strict locator in first arg, attributes in second + * $I->seeElement(['css' => 'form input'], ['name' => 'login']); + * ``` + * @see \Codeception\Module\WebDriver::seeElement() + */ + public function canSeeElement($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeElement', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the given element is invisible or not present on the page. + * You can also specify expected attributes of this element. + * + * ```php + * dontSeeElement('.error'); + * $I->dontSeeElement('//form/input[1]'); + * $I->dontSeeElement('input', ['name' => 'login']); + * $I->dontSeeElement('input', ['value' => '123456']); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeElement() + */ + public function dontSeeElement($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeElement', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the given element is invisible or not present on the page. + * You can also specify expected attributes of this element. + * + * ```php + * dontSeeElement('.error'); + * $I->dontSeeElement('//form/input[1]'); + * $I->dontSeeElement('input', ['name' => 'login']); + * $I->dontSeeElement('input', ['value' => '123456']); + * ``` + * @see \Codeception\Module\WebDriver::dontSeeElement() + */ + public function cantSeeElement($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeElement', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the given element exists on the page, even it is invisible. + * + * ``` php + * seeElementInDOM('//form/input[type=hidden]'); + * ``` + * + * @param string|array|WebDriverBy $selector + * @see \Codeception\Module\WebDriver::seeElementInDOM() + */ + public function seeElementInDOM($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeElementInDOM', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the given element exists on the page, even it is invisible. + * + * ``` php + * seeElementInDOM('//form/input[type=hidden]'); + * ``` + * + * @param string|array|WebDriverBy $selector + * @see \Codeception\Module\WebDriver::seeElementInDOM() + */ + public function canSeeElementInDOM($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeElementInDOM', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Opposite of `seeElementInDOM`. + * + * @param string|array|WebDriverBy $selector + * @see \Codeception\Module\WebDriver::dontSeeElementInDOM() + */ + public function dontSeeElementInDOM($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeElementInDOM', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Opposite of `seeElementInDOM`. + * + * @param string|array|WebDriverBy $selector + * @see \Codeception\Module\WebDriver::dontSeeElementInDOM() + */ + public function cantSeeElementInDOM($selector, array $attributes = []): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeElementInDOM', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that there are a certain number of elements matched by the given locator on the page. + * + * ```php + * seeNumberOfElements('tr', 10); + * $I->seeNumberOfElements('tr', [0,10]); // between 0 and 10 elements + * ``` + * + * @param int|int[] $expected + * @see \Codeception\Module\WebDriver::seeNumberOfElements() + */ + public function seeNumberOfElements($selector, $expected): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumberOfElements', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that there are a certain number of elements matched by the given locator on the page. + * + * ```php + * seeNumberOfElements('tr', 10); + * $I->seeNumberOfElements('tr', [0,10]); // between 0 and 10 elements + * ``` + * + * @param int|int[] $expected + * @see \Codeception\Module\WebDriver::seeNumberOfElements() + */ + public function canSeeNumberOfElements($selector, $expected): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberOfElements', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param string|array|WebDriverBy $selector + * @param int|array $expected + * @throws ModuleException + * @see \Codeception\Module\WebDriver::seeNumberOfElementsInDOM() + */ + public function seeNumberOfElementsInDOM($selector, $expected) { + return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeNumberOfElementsInDOM', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * @param string|array|WebDriverBy $selector + * @param int|array $expected + * @throws ModuleException + * @see \Codeception\Module\WebDriver::seeNumberOfElementsInDOM() + */ + public function canSeeNumberOfElementsInDOM($selector, $expected) { + return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeNumberOfElementsInDOM', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the given option is selected. + * + * ```php + * seeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::seeOptionIsSelected() + */ + public function seeOptionIsSelected($selector, $optionText): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeOptionIsSelected', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the given option is selected. + * + * ```php + * seeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::seeOptionIsSelected() + */ + public function canSeeOptionIsSelected($selector, $optionText): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeOptionIsSelected', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the given option is not selected. + * + * ```php + * dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::dontSeeOptionIsSelected() + */ + public function dontSeeOptionIsSelected($selector, $optionText): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeOptionIsSelected', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the given option is not selected. + * + * ```php + * dontSeeOptionIsSelected('#form input[name=payment]', 'Visa'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::dontSeeOptionIsSelected() + */ + public function cantSeeOptionIsSelected($selector, $optionText): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeOptionIsSelected', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the page title contains the given string. + * + * ```php + * seeInTitle('Blog - Post #1'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::seeInTitle() + */ + public function seeInTitle($title) { + return $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInTitle', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the page title contains the given string. + * + * ```php + * seeInTitle('Blog - Post #1'); + * ``` + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::seeInTitle() + */ + public function canSeeInTitle($title) { + return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInTitle', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the page title does not contain the given string. + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::dontSeeInTitle() + */ + public function dontSeeInTitle($title) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInTitle', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the page title does not contain the given string. + * + * @return mixed|void + * @see \Codeception\Module\WebDriver::dontSeeInTitle() + */ + public function cantSeeInTitle($title) { + return $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInTitle', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Accepts the active JavaScript native popup window, as created by `window.alert`|`window.confirm`|`window.prompt`. + * Don't confuse popups with modal windows, + * as created by [various libraries](https://jster.net/category/windows-modals-popups). + * @see \Codeception\Module\WebDriver::acceptPopup() + */ + public function acceptPopup(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('acceptPopup', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Dismisses the active JavaScript popup, as created by `window.alert`, `window.confirm`, or `window.prompt`. + * @see \Codeception\Module\WebDriver::cancelPopup() + */ + public function cancelPopup(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('cancelPopup', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the active JavaScript popup, + * as created by `window.alert`|`window.confirm`|`window.prompt`, contains the given string. + * + * @throws ModuleException + * @see \Codeception\Module\WebDriver::seeInPopup() + */ + public function seeInPopup(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Assertion('seeInPopup', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the active JavaScript popup, + * as created by `window.alert`|`window.confirm`|`window.prompt`, contains the given string. + * + * @throws ModuleException + * @see \Codeception\Module\WebDriver::seeInPopup() + */ + public function canSeeInPopup(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('seeInPopup', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the active JavaScript popup, + * as created by `window.alert`|`window.confirm`|`window.prompt`, does NOT contain the given string. + * + * @throws ModuleException + * @see \Codeception\Module\WebDriver::dontSeeInPopup() + */ + public function dontSeeInPopup(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('dontSeeInPopup', func_get_args())); + } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * [!] Conditional Assertion: Test won't be stopped on fail + * Checks that the active JavaScript popup, + * as created by `window.alert`|`window.confirm`|`window.prompt`, does NOT contain the given string. + * + * @throws ModuleException + * @see \Codeception\Module\WebDriver::dontSeeInPopup() + */ + public function cantSeeInPopup(string $text): void { + $this->getScenario()->runStep(new \Codeception\Step\ConditionalAssertion('dontSeeInPopup', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Enters text into a native JavaScript prompt popup, as created by `window.prompt`. + * + * @throws ModuleException + * @see \Codeception\Module\WebDriver::typeInPopup() + */ + public function typeInPopup(string $keys): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('typeInPopup', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Reloads the current page. + * @see \Codeception\Module\WebDriver::reloadPage() + */ + public function reloadPage(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('reloadPage', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Moves back in history. + * @see \Codeception\Module\WebDriver::moveBack() + */ + public function moveBack(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('moveBack', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Moves forward in history. + * @see \Codeception\Module\WebDriver::moveForward() + */ + public function moveForward(): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('moveForward', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Submits the given form on the page, optionally with the given form + * values. Give the form fields values as an array. Note that hidden fields + * can't be accessed. + * + * Skipped fields will be filled by their values from the page. + * You don't need to click the 'Submit' button afterwards. + * This command itself triggers the request to form's action. + * + * You can optionally specify what button's value to include + * in the request with the last parameter as an alternative to + * explicitly setting its value in the second parameter, as + * button values are not otherwise included in the request. + * + * Examples: + * + * ``` php + * submitForm('#login', [ + * 'login' => 'davert', + * 'password' => '123456' + * ]); + * // or + * $I->submitForm('#login', [ + * 'login' => 'davert', + * 'password' => '123456' + * ], 'submitButtonName'); + * + * ``` + * + * For example, given this sample "Sign Up" form: + * + * ``` html + *
      + * Login: + *
      + * Password: + *
      + * Do you agree to our terms? + *
      + * Select pricing plan: + * + * + *
      + * ``` + * + * You could write the following to submit it: + * + * ``` php + * submitForm( + * '#userForm', + * [ + * 'user[login]' => 'Davert', + * 'user[password]' => '123456', + * 'user[agree]' => true + * ], + * 'submitButton' + * ); + * ``` + * Note that "2" will be the submitted value for the "plan" field, as it is + * the selected option. + * + * Also note that this differs from PhpBrowser, in that + * ```'user' => [ 'login' => 'Davert' ]``` is not supported at the moment. + * Named array keys *must* be included in the name as above. + * + * Pair this with seeInFormFields for quick testing magic. + * + * ``` php + * 'value', + * 'field2' => 'another value', + * 'checkbox1' => true, + * // ... + * ]; + * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); + * // $I->amOnPage('/path/to/form-page') may be needed + * $I->seeInFormFields('//form[@id=my-form]', $form); + * ``` + * + * Parameter values must be set to arrays for multiple input fields + * of the same name, or multi-select combo boxes. For checkboxes, + * either the string value can be used, or boolean values which will + * be replaced by the checkbox's value in the DOM. + * + * ``` php + * submitForm('#my-form', [ + * 'field1' => 'value', + * 'checkbox' => [ + * 'value of first checkbox', + * 'value of second checkbox', + * ], + * 'otherCheckboxes' => [ + * true, + * false, + * false, + * ], + * 'multiselect' => [ + * 'first option value', + * 'second option value', + * ] + * ]); + * ``` + * + * Mixing string and boolean values for a checkbox's value is not supported + * and may produce unexpected results. + * + * Field names ending in "[]" must be passed without the trailing square + * bracket characters, and must contain an array for its value. This allows + * submitting multiple values with the same name, consider: + * + * ```php + * $I->submitForm('#my-form', [ + * 'field[]' => 'value', + * 'field[]' => 'another value', // 'field[]' is already a defined key + * ]); + * ``` + * + * The solution is to pass an array value: + * + * ```php + * // this way both values are submitted + * $I->submitForm('#my-form', [ + * 'field' => [ + * 'value', + * 'another value', + * ] + * ]); + * ``` + * + * The `$button` parameter can be either a string, an array or an instance + * of Facebook\WebDriver\WebDriverBy. When it is a string, the + * button will be found by its "name" attribute. If $button is an + * array then it will be treated as a strict selector and a WebDriverBy + * will be used verbatim. + * + * For example, given the following HTML: + * + * ``` html + * + * ``` + * + * `$button` could be any one of the following: + * - 'submitButton' + * - ['name' => 'submitButton'] + * - WebDriverBy::name('submitButton') + * + * @param string|array|WebDriverBy $selector + * @param string|array|WebDriverBy|null $button + * @see \Codeception\Module\WebDriver::submitForm() + */ + public function submitForm($selector, array $params, $button = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('submitForm', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Waits up to $timeout seconds for the given element to change. + * Element "change" is determined by a callback function which is called repeatedly + * until the return value evaluates to true. + * + * ``` php + * waitForElementChange('#menu', function(WebDriverElement $el) { + * return $el->isDisplayed(); + * }, 100); + * ``` + * + * @param string|array|WebDriverBy $element + * @throws ElementNotFound + * @see \Codeception\Module\WebDriver::waitForElementChange() + */ + public function waitForElementChange($element, \Closure $callback, int $timeout = 30): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('waitForElementChange', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Waits up to $timeout seconds for an element to appear on the page. + * If the element doesn't appear, a timeout exception is thrown. + * + * ``` php + * waitForElement('#agree_button', 30); // secs + * $I->click('#agree_button'); + * ``` + * + * @param string|array|WebDriverBy $element + * @param int $timeout seconds + * @throws Exception + * @see \Codeception\Module\WebDriver::waitForElement() + */ + public function waitForElement($element, int $timeout = 10): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('waitForElement', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Waits up to $timeout seconds for the given element to be visible on the page. + * If element doesn't appear, a timeout exception is thrown. + * + * ``` php + * waitForElementVisible('#agree_button', 30); // secs + * $I->click('#agree_button'); + * ``` + * + * @param string|array|WebDriverBy $element + * @param int $timeout seconds + * @throws Exception + * @see \Codeception\Module\WebDriver::waitForElementVisible() + */ + public function waitForElementVisible($element, int $timeout = 10): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('waitForElementVisible', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Waits up to $timeout seconds for the given element to become invisible. + * If element stays visible, a timeout exception is thrown. + * + * ``` php + * waitForElementNotVisible('#agree_button', 30); // secs + * ``` + * + * @param string|array|WebDriverBy $element + * @param int $timeout seconds + * @throws Exception + * @see \Codeception\Module\WebDriver::waitForElementNotVisible() + */ + public function waitForElementNotVisible($element, int $timeout = 10): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('waitForElementNotVisible', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Waits up to $timeout seconds for the given element to be clickable. + * If element doesn't become clickable, a timeout exception is thrown. + * + * ``` php + * waitForElementClickable('#agree_button', 30); // secs + * $I->click('#agree_button'); + * ``` + * + * @param string|array|WebDriverBy $element + * @param int $timeout seconds + * @throws Exception + * @see \Codeception\Module\WebDriver::waitForElementClickable() + */ + public function waitForElementClickable($element, int $timeout = 10): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('waitForElementClickable', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Waits up to $timeout seconds for the given string to appear on the page. + * + * Can also be passed a selector to search in, be as specific as possible when using selectors. + * waitForText() will only watch the first instance of the matching selector / text provided. + * If the given text doesn't appear, a timeout exception is thrown. + * + * ``` php + * waitForText('foo', 30); // secs + * $I->waitForText('foo', 30, '.title'); // secs + * ``` + * + * @param int $timeout seconds + * @param null|string|array|WebDriverBy $selector + * @throws Exception + * @see \Codeception\Module\WebDriver::waitForText() + */ + public function waitForText(string $text, int $timeout = 10, $selector = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('waitForText', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Wait for $timeout seconds. + * + * @param int|float $timeout secs + * @throws TestRuntimeException + * @see \Codeception\Module\WebDriver::wait() + */ + public function wait($timeout): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('wait', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Low-level API method. + * If Codeception commands are not enough, this allows you to use Selenium WebDriver methods directly: + * + * ``` php + * $I->executeInSelenium(function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { + * $webdriver->get('https://google.com'); + * }); + * ``` + * + * This runs in the context of the + * [RemoteWebDriver class](https://github.com/php-webdriver/php-webdriver/blob/master/lib/remote/RemoteWebDriver.php). + * Try not to use this command on a regular basis. + * If Codeception lacks a feature you need, please implement it and submit a patch. + * + * @param Closure $function + * @return mixed + * @see \Codeception\Module\WebDriver::executeInSelenium() + */ + public function executeInSelenium(\Closure $function) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('executeInSelenium', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Switch to another window identified by name. + * + * The window can only be identified by name. If the $name parameter is blank, the parent window will be used. + * + * Example: + * ``` html + * + * ``` + * + * ``` php + * click("Open window"); + * # switch to another window + * $I->switchToWindow("another_window"); + * # switch to parent window + * $I->switchToWindow(); + * ``` + * + * If the window has no name, match it by switching to next active tab using `switchToNextTab` method. + * + * Or use native Selenium functions to get access to all opened windows: + * + * ``` php + * executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { + * $handles=$webdriver->getWindowHandles(); + * $last_window = end($handles); + * $webdriver->switchTo()->window($last_window); + * }); + * ``` + * @see \Codeception\Module\WebDriver::switchToWindow() + */ + public function switchToWindow(?string $name = NULL): void { + $this->getScenario()->runStep(new \Codeception\Step\Action('switchToWindow', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Switch to another iframe on the page. + * + * Example: + * ``` html + * + From 50d112414004db0033ea221721b80de3d39f536e Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Sat, 9 Mar 2024 23:59:02 +0100 Subject: [PATCH 63/66] feat: add test for loaded form elements --- Tests/Acceptance/Backend/ElementCest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Acceptance/Backend/ElementCest.php b/Tests/Acceptance/Backend/ElementCest.php index 203009f..b19c089 100644 --- a/Tests/Acceptance/Backend/ElementCest.php +++ b/Tests/Acceptance/Backend/ElementCest.php @@ -44,6 +44,7 @@ public function seeFormSelection( $extensionConfiguration->write('formcycleClientId', '2252'); $this->navigateToElementTab($I, $pageTree); $I->dontSee('Configuration error'); + $I->waitForElementVisible('#xm-available-forms-wrapper'); } private function navigateToElementTab( From 642e0407235d45c1597e1ee56a02a3e75d74cc09 Mon Sep 17 00:00:00 2001 From: Maik Schneider Date: Mon, 11 Mar 2024 10:22:51 +0100 Subject: [PATCH 64/66] feat: remove loadResponseJs setting, always include via xfc-rp-usebs parameter --- Classes/Dto/ElementSettings.php | 3 --- Classes/Service/FormcycleService.php | 2 +- Configuration/FlexForms/flexform_list.xml | 18 ------------------ Resources/Private/Language/locallang.xlf | 9 --------- Tests/Unit/Dto/ElementSettingsTest.php | 1 - 5 files changed, 1 insertion(+), 32 deletions(-) diff --git a/Classes/Dto/ElementSettings.php b/Classes/Dto/ElementSettings.php index 371a7f8..2490060 100644 --- a/Classes/Dto/ElementSettings.php +++ b/Classes/Dto/ElementSettings.php @@ -19,8 +19,6 @@ class ElementSettings public bool $loadFormcycleJqueryUi = false; - public bool $loadResponseJs = false; - public string $additionalParameters = ''; public static function createFromContentElement( @@ -36,7 +34,6 @@ public static function createFromContentElement( $settings->errorPid = $xml['settings']['xf']['siteerror'] ?? 0; $settings->loadFormcycleJquery = (bool)($xml['settings']['xf']['useFcjQuery'] ?? 1); $settings->loadFormcycleJqueryUi = (bool)($xml['settings']['xf']['useFcjQueryUi'] ?? 0); - $settings->loadResponseJs = (bool)($xml['settings']['xf']['useFcBootStrap'] ?? 0); $settings->additionalParameters = $xml['settings']['xf']['useFcUrlParams'] ?? ''; $settings->integrationMode = IntegrationMode::tryFrom($xml['settings']['xf']['integrationMode']); diff --git a/Classes/Service/FormcycleService.php b/Classes/Service/FormcycleService.php index 133a398..81d3e7c 100644 --- a/Classes/Service/FormcycleService.php +++ b/Classes/Service/FormcycleService.php @@ -142,7 +142,7 @@ private function getCommonQueryParams(ElementSettings $settings): array 'xfc-rp-inline' => true, 'xfc-rp-usejq' => 0, 'xfc-rp-useui' => 0, - 'xfc-rp-usebs' => $settings->loadResponseJs ? 1 : 0, + 'xfc-rp-usebs' => 1, 'xfc-pp-external' => true, 'xfc-pp-base-url' => '', 'xfc-pp-use-base-url' => false, diff --git a/Configuration/FlexForms/flexform_list.xml b/Configuration/FlexForms/flexform_list.xml index 5b1b493..6f5125f 100644 --- a/Configuration/FlexForms/flexform_list.xml +++ b/Configuration/FlexForms/flexform_list.xml @@ -140,24 +140,6 @@ - - - - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.bootstrap.description - - - check - 0 - - - LLL:EXT:xm_formcycle/Resources/Private/Language/locallang.xlf:flexform.bootstrap.checkbox - 0 - - - -