This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,14 @@
<?php die(); ?>
[2024-02-06 06:38:17] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Undefined constant "GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE" in /nas/content/live/templategf/wp-content/plugins/duracelltomi-google-tag-manager/public/frontend.php:1324
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/duracelltomi-google-tag-manager/duracelltomi-google-tag-manager-for-wordpress.php(49): require_once()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): gtm4wp_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-settings.php(506): do_action('plugins_loaded')
#5 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#6 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-blog-header.php(13): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/index.php(17): require('/nas/content/li...')
#9 {main}
thrown on line 1324 in file wp-content/plugins/duracelltomi-google-tag-manager/public/frontend.php
@@ -0,0 +1,14 @@
<?php die(); ?>
[2024-02-12 10:33:14] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Undefined constant "GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE" in /nas/content/live/templategf/wp-content/plugins/duracelltomi-google-tag-manager/public/frontend.php:1324
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/duracelltomi-google-tag-manager/duracelltomi-google-tag-manager-for-wordpress.php(49): require_once()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): gtm4wp_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-settings.php(506): do_action('plugins_loaded')
#5 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#6 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-blog-header.php(13): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/index.php(17): require('/nas/content/li...')
#9 {main}
thrown on line 1324 in file wp-content/plugins/duracelltomi-google-tag-manager/public/frontend.php
@@ -0,0 +1,58 @@
<?php die(); ?>
[2024-07-04 06:14:22] ERROR Task ChildVerify ended with PHP fatal error: Uncaught ArgumentCountError: Too few arguments to function WP_Rocket\Engine\Media\ImageDimensions\Subscriber::__construct(), 1 passed and exactly 2 expected in /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Engine/Media/ImageDimensions/Subscriber.php:35
Stack trace:
#0 [internal function]: WP_Rocket\Engine\Media\ImageDimensions\Subscriber->__construct(Object(WP_Rocket\Engine\Media\ImageDimensions\ImageDimensions))
#1 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/Definition.php(256): ReflectionClass->newInstanceArgs(Array)
#2 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/Definition.php(212): WP_Rocket\Dependencies\League\Container\Definition\Definition->resolveClass('WP_Rocket\\Engin...')
#3 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/DefinitionAggregate.php(94): WP_Rocket\Dependencies\League\Container\Definition\Definition->resolve(false)
#4 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Container.php(157): WP_Rocket\Dependencies\League\Container\Definition\DefinitionAggregate->resolve('image_dimension...', false)
#5 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Plugin.php(161): WP_Rocket\Dependencies\League\Container\Container->get('image_dimension...')
#6 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/main.php(47): WP_Rocket\Plugin->load()
#7 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): rocket_init('')
#8 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#9 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#10 /nas/content/live/templategf/wp-settings.php(550): do_action('plugins_loaded')
#11 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#12 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#13 /nas/content/live/templategf/wp-blog-header.php(13): require_once('/nas/content/li...')
#14 /nas/content/live/templategf/index.php(17): require('/nas/content/li...')
#15 {main}
thrown on line 35 in file wp-content/plugins/wp-rocket/inc/Engine/Media/ImageDimensions/Subscriber.php
[2024-07-04 06:14:25] ERROR Task ChildVerify ended with PHP fatal error: Uncaught ArgumentCountError: Too few arguments to function WP_Rocket\Engine\Media\ImageDimensions\Subscriber::__construct(), 1 passed and exactly 2 expected in /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Engine/Media/ImageDimensions/Subscriber.php:35
Stack trace:
#0 [internal function]: WP_Rocket\Engine\Media\ImageDimensions\Subscriber->__construct(Object(WP_Rocket\Engine\Media\ImageDimensions\ImageDimensions))
#1 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/Definition.php(256): ReflectionClass->newInstanceArgs(Array)
#2 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/Definition.php(212): WP_Rocket\Dependencies\League\Container\Definition\Definition->resolveClass('WP_Rocket\\Engin...')
#3 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/DefinitionAggregate.php(94): WP_Rocket\Dependencies\League\Container\Definition\Definition->resolve(false)
#4 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Container.php(157): WP_Rocket\Dependencies\League\Container\Definition\DefinitionAggregate->resolve('image_dimension...', false)
#5 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Plugin.php(161): WP_Rocket\Dependencies\League\Container\Container->get('image_dimension...')
#6 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/main.php(47): WP_Rocket\Plugin->load()
#7 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): rocket_init('')
#8 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#9 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#10 /nas/content/live/templategf/wp-settings.php(550): do_action('plugins_loaded')
#11 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#12 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#13 /nas/content/live/templategf/wp-blog-header.php(13): require_once('/nas/content/li...')
#14 /nas/content/live/templategf/index.php(17): require('/nas/content/li...')
#15 {main}
thrown on line 35 in file wp-content/plugins/wp-rocket/inc/Engine/Media/ImageDimensions/Subscriber.php
[2024-07-04 06:14:29] ERROR Task ChildVerify ended with PHP fatal error: Uncaught ArgumentCountError: Too few arguments to function WP_Rocket\Engine\Media\ImageDimensions\Subscriber::__construct(), 1 passed and exactly 2 expected in /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Engine/Media/ImageDimensions/Subscriber.php:35
Stack trace:
#0 [internal function]: WP_Rocket\Engine\Media\ImageDimensions\Subscriber->__construct(Object(WP_Rocket\Engine\Media\ImageDimensions\ImageDimensions))
#1 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/Definition.php(256): ReflectionClass->newInstanceArgs(Array)
#2 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/Definition.php(212): WP_Rocket\Dependencies\League\Container\Definition\Definition->resolveClass('WP_Rocket\\Engin...')
#3 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Definition/DefinitionAggregate.php(94): WP_Rocket\Dependencies\League\Container\Definition\Definition->resolve(false)
#4 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Dependencies/League/Container/Container.php(157): WP_Rocket\Dependencies\League\Container\Definition\DefinitionAggregate->resolve('image_dimension...', false)
#5 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/Plugin.php(161): WP_Rocket\Dependencies\League\Container\Container->get('image_dimension...')
#6 /nas/content/live/templategf/wp-content/plugins/wp-rocket/inc/main.php(47): WP_Rocket\Plugin->load()
#7 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): rocket_init('')
#8 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#9 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#10 /nas/content/live/templategf/wp-settings.php(550): do_action('plugins_loaded')
#11 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#12 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#13 /nas/content/live/templategf/wp-blog-header.php(13): require_once('/nas/content/li...')
#14 /nas/content/live/templategf/index.php(17): require('/nas/content/li...')
#15 {main}
thrown on line 35 in file wp-content/plugins/wp-rocket/inc/Engine/Media/ImageDimensions/Subscriber.php
@@ -0,0 +1,349 @@
<?php die(); ?>
[2024-07-16 07:58:49] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:49] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:49] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:49] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:52] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:52] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:52] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:52] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:56] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:56] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:57] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 07:58:57] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
[2024-07-16 08:07:05] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:05] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:05] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:06] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:08] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:08] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:08] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:09] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:13] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:13] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:13] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-16 08:07:14] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
@@ -0,0 +1,206 @@
<?php die(); ?>
[2024-07-23 06:09:26] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:26] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:26] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:27] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:29] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:29] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:29] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:29] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:34] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:34] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:34] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:09:34] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
[2024-07-23 06:22:43] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "Elementor\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor/core/modules-manager.php:53
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(730): Elementor\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(647): Elementor\Plugin->init_components()
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#3 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#4 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#6 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#7 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#8 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#9 {main}
thrown on line 53 in file wp-content/plugins/elementor/core/modules-manager.php
@@ -0,0 +1,17 @@
<?php die(); ?>
[2024-08-01 10:35:28] ERROR Task ChildVerify ended with PHP fatal error: Uncaught Error: Class "\ElementorPro\Modules\ConversionCenter\Module" not found in /nas/content/live/templategf/wp-content/plugins/elementor-pro/core/modules-manager.php:84
Stack trace:
#0 /nas/content/live/templategf/wp-content/plugins/elementor-pro/plugin.php(361): ElementorPro\Core\Modules_Manager->__construct()
#1 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): ElementorPro\Plugin->on_elementor_init('')
#2 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#3 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#4 /nas/content/live/templategf/wp-content/plugins/elementor/includes/plugin.php(658): do_action('elementor/init')
#5 /nas/content/live/templategf/wp-includes/class-wp-hook.php(324): Elementor\Plugin->init('')
#6 /nas/content/live/templategf/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array)
#7 /nas/content/live/templategf/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#8 /nas/content/live/templategf/wp-settings.php(695): do_action('init')
#9 /nas/content/live/templategf/wp-config.php(68): require_once('/nas/content/li...')
#10 /nas/content/live/templategf/wp-load.php(50): require_once('/nas/content/li...')
#11 /nas/content/live/templategf/wp-admin/admin-ajax.php(22): require_once('/nas/content/li...')
#12 {main}
thrown on line 84 in file wp-content/plugins/elementor-pro/core/modules-manager.php
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
DENY FROM ALL
+1
View File
@@ -0,0 +1 @@
<?php // Silence is gold. ?>
+61
View File
@@ -0,0 +1,61 @@
<?php
/**
* Plugin Name: SQLite integration (Drop-in)
* Version: 1.0.0
* Author: WordPress Performance Team
* Author URI: https://make.wordpress.org/performance/
*
* This file is auto-generated and copied from the sqlite plugin.
* Please don't edit this file directly.
*
* @package wp-sqlite-integration
*/
define( 'SQLITE_DB_DROPIN_VERSION', '1.8.0' );
// Tweak to allow copy-pasting the file without having to run string-replacements.
$sqlite_plugin_implementation_folder_path = realpath( __DIR__ . '/mu-plugins/sqlite-database-integration' );
if ( ! file_exists( $sqlite_plugin_implementation_folder_path ) ) { // Check that the folder exists.
$sqlite_plugin_implementation_folder_path = realpath( __DIR__ . '/plugins/sqlite-database-integration' );
}
// Bail early if the SQLite implementation was not located in the plugin.
if ( ! $sqlite_plugin_implementation_folder_path || ! file_exists( $sqlite_plugin_implementation_folder_path . '/wp-includes/sqlite/db.php' ) ) {
return;
}
// Constant for backward compatibility.
if ( ! defined( 'DATABASE_TYPE' ) ) {
define( 'DATABASE_TYPE', 'sqlite' );
}
// Define SQLite constant.
if ( ! defined( 'DB_ENGINE' ) ) {
define( 'DB_ENGINE', 'sqlite' );
}
// Require the implementation from the plugin.
require_once $sqlite_plugin_implementation_folder_path . '/wp-includes/sqlite/db.php';
// Activate the performance-lab plugin if it is not already activated.
add_action(
'admin_footer',
function() {
if ( defined( 'SQLITE_MAIN_FILE' ) ) {
return;
}
if ( ! function_exists( 'activate_plugin' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_path = WP_PLUGIN_DIR . '/' . '{SQLITE_PLUGIN}';
if ( file_exists( $plugin_path ) && is_plugin_inactive( '{SQLITE_PLUGIN}' ) ) {
// If `activate_plugin()` returns a value other than null (like WP_Error),
// the plugin could not be found. Try with a hardcoded string,
// because that probably means the file was directly copy-pasted.
if ( null !== activate_plugin( '{SQLITE_PLUGIN}', '', false, true ) ) {
activate_plugin( 'sqlite-database-integration/load.php', '', false, true );
}
}
}
);
+2
View File
@@ -0,0 +1,2 @@
<?php
// Silence is golden.
@@ -0,0 +1 @@
<?php
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
<?php
+1
View File
@@ -0,0 +1 @@
<?php
@@ -0,0 +1,34 @@
<?php
/**
* Studio MU-Plugins Loader
* Loads Studio-specific mu-plugins from a Studio-managed directory.
*/
// Define database constants if not already defined. It fixes the error
// for imported sites that don't have those defined e.g. WP Cloud and
// include plugins which try to access those directly e.g. Mailpoet
if ( ! defined( 'DB_NAME' ) ) define( 'DB_NAME', 'database_name_here' );
if ( ! defined( 'DB_USER' ) ) define( 'DB_USER', 'username_here' );
if ( ! defined( 'DB_PASSWORD' ) ) define( 'DB_PASSWORD', 'password_here' );
if ( ! defined( 'DB_HOST' ) ) define( 'DB_HOST', 'localhost' );
if ( ! defined( 'DB_CHARSET' ) ) define( 'DB_CHARSET', 'utf8' );
if ( ! defined( 'DB_COLLATE' ) ) define( 'DB_COLLATE', '' );
// Set environment type to local if not already defined
if ( ! defined( 'WP_ENVIRONMENT_TYPE' ) ) define( 'WP_ENVIRONMENT_TYPE', 'local' );
$studio_mu_plugins_dir = 'C:\\Users\\Knossos\\AppData\\Local\\Temp\\studio-mu-plugins-5kcqeQ';
if ( is_dir( $studio_mu_plugins_dir ) ) {
$files = glob( $studio_mu_plugins_dir . '/*.php' );
if ( $files ) {
// Sort files to ensure consistent loading order
sort( $files );
foreach ( $files as $file ) {
if ( is_file( $file ) ) {
require_once $file;
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
<?php
//silence is golden
@@ -0,0 +1,64 @@
<?php // Pressable Cache Management mu-plugins index
// disable direct file access
if (!defined('ABSPATH'))
{
exit;
}
/*****
* This file references the Pressable Cache Management mu-plugins
* https://wordpress.org/documentation/article/must-use-plugins/
******/
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_extend_batcache.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_extend_batcache.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_specific_file.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_specific_file.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_cdn_extender.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_cdn_extender.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_jpg_png_webp.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_jpg_png_webp.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_css.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_css.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_js_json.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/cdn_exclude_js_json.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_exclude_font_files_from_cdn.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_exclude_font_files_from_cdn.php';
}
// if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/batcache_manager.php')) {
// require WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_batcache_manager.php.php';
// }
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_cache_wpp_cookies_pages.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_cache_wpp_cookies_pages.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_exclude_query_string_gclid.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_exclude_query_string_gclid.php';
}
if(file_exists(WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_exclude_pages_from_batcache.php')) {
require WPMU_PLUGIN_DIR.'/pressable-cache-management/pcm_exclude_pages_from_batcache.php';
}
@@ -0,0 +1,339 @@
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.
GNU GENERAL PUBLIC LICENSE
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
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,134 @@
<?php
/**
* Handle the SQLite activation.
*
* @since 1.0.0
* @package wp-sqlite-integration
*/
/**
* Redirect to the plugin's admin screen on activation.
*
* @since 1.0.0
*
* @param string $plugin The plugin basename.
*/
function sqlite_plugin_activation_redirect( $plugin ) {
if ( plugin_basename( SQLITE_MAIN_FILE ) === $plugin ) {
if ( wp_safe_redirect( admin_url( 'options-general.php?page=sqlite-integration' ) ) ) {
exit;
}
}
}
add_action( 'activated_plugin', 'sqlite_plugin_activation_redirect' );
/**
* Check the URL to ensure we're on the plugin page,
* the user has clicked the button to install SQLite,
* and the nonce is valid.
* If the above conditions are met, run the sqlite_plugin_copy_db_file() function,
* and redirect to the install screen.
*
* @since 1.0.0
*/
function sqlite_activation() {
global $current_screen;
if ( isset( $current_screen->base ) && 'settings_page_sqlite-integration' === $current_screen->base ) {
return;
}
if ( isset( $_GET['confirm-install'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'sqlite-install' ) ) {
// Handle upgrading from the performance-lab plugin.
if ( isset( $_GET['upgrade-from-pl'] ) ) {
global $wp_filesystem;
require_once ABSPATH . '/wp-admin/includes/file.php';
// Delete the previous db.php file.
$wp_filesystem->delete( WP_CONTENT_DIR . '/db.php' );
// Deactivate the performance-lab SQLite module.
$pl_option_name = defined( 'PERFLAB_MODULES_SETTING' ) ? PERFLAB_MODULES_SETTING : 'perflab_modules_settings';
$pl_option = get_option( $pl_option_name, array() );
unset( $pl_option['database/sqlite'] );
update_option( $pl_option_name, $pl_option );
}
sqlite_plugin_copy_db_file();
// WordPress will automatically redirect to the install screen here.
wp_redirect( admin_url() );
exit;
}
}
add_action( 'admin_init', 'sqlite_activation' );
// Flush the cache at the last moment before the redirect.
add_filter(
'x_redirect_by',
function ( $result ) {
wp_cache_flush();
return $result;
},
PHP_INT_MAX,
1
);
/**
* Add the db.php file in wp-content.
*
* When the plugin gets merged in wp-core, this is not to be ported.
*/
function sqlite_plugin_copy_db_file() {
// Bail early if the PDO SQLite extension is not loaded.
if ( ! extension_loaded( 'pdo_sqlite' ) ) {
return;
}
$destination = WP_CONTENT_DIR . '/db.php';
/*
* When an existing "db.php" drop-in is detected, let's check if it's a known
* plugin that we can continue supporting even when we override the drop-in.
*/
$override_db_dropin = false;
if ( file_exists( $destination ) ) {
// Check for the Query Monitor plugin.
// When "QM_DB" exists, it must have been loaded via the "db.php" file.
if ( class_exists( 'QM_DB', false ) ) {
$override_db_dropin = true;
}
if ( $override_db_dropin ) {
require_once ABSPATH . '/wp-admin/includes/file.php';
global $wp_filesystem;
if ( ! $wp_filesystem ) {
WP_Filesystem();
}
$wp_filesystem->delete( $destination );
}
}
// Place database drop-in if not present yet, except in case there is
// another database drop-in present already.
if ( ! defined( 'SQLITE_DB_DROPIN_VERSION' ) && ! file_exists( $destination ) ) {
// Init the filesystem to allow copying the file.
global $wp_filesystem;
require_once ABSPATH . '/wp-admin/includes/file.php';
// Init the filesystem if needed, then copy the file, replacing contents as needed.
if ( ( $wp_filesystem || WP_Filesystem() ) && $wp_filesystem->touch( $destination ) ) {
// Get the db.copy.php file contents, replace placeholders and write it to the destination.
$file_contents = str_replace(
array(
'{SQLITE_IMPLEMENTATION_FOLDER_PATH}',
'{SQLITE_PLUGIN}',
),
array(
__DIR__,
str_replace( WP_PLUGIN_DIR . '/', '', SQLITE_MAIN_FILE ),
),
file_get_contents( __DIR__ . '/db.copy' )
);
$wp_filesystem->put_contents( $destination, $file_contents );
}
}
}
@@ -0,0 +1,76 @@
<?php
/**
* Functions to add admin notices if necessary.
*
* @since 1.0.0
* @package wp-sqlite-integration
*/
/**
* Add admin notices.
*
* When the plugin gets merged in wp-core, this is not to be ported.
*/
function sqlite_plugin_admin_notice() {
// Don't print notices in the plugin's admin screen.
global $current_screen;
if ( isset( $current_screen->base ) && 'settings_page_sqlite-integration' === $current_screen->base ) {
return;
}
// If PDO SQLite is not loaded, bail early.
if ( ! extension_loaded( 'pdo_sqlite' ) ) {
printf(
'<div class="notice notice-error"><p>%s</p></div>',
esc_html__( 'The SQLite Integration plugin is active, but the PDO SQLite extension is missing from your server. Please make sure that PDO SQLite is enabled in your PHP installation.', 'sqlite-database-integration' )
);
return;
}
/*
* If the SQLITE_DB_DROPIN_VERSION constant is not defined
* but there's a db.php file in the wp-content directory, then the module can't be activated.
* The module should not have been activated in the first place
* (there's a check in the can-load.php file), but this is a fallback check.
*/
if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && ! defined( 'SQLITE_DB_DROPIN_VERSION' ) ) {
printf(
'<div class="notice notice-error"><p>%s</p></div>',
sprintf(
/* translators: 1: SQLITE_DB_DROPIN_VERSION constant, 2: db.php drop-in path */
__( 'The SQLite Integration module is active, but the %1$s constant is missing. It appears you already have another %2$s file present on your site. ', 'sqlite-database-integration' ),
'<code>SQLITE_DB_DROPIN_VERSION</code>',
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php</code>'
)
);
return;
}
if ( file_exists( WP_CONTENT_DIR . '/db.php' ) ) {
return;
}
if ( ! wp_is_writable( WP_CONTENT_DIR ) ) {
printf(
'<div class="notice notice-error"><p>%s</p></div>',
esc_html__( 'The SQLite Integration plugin is active, but the wp-content/db.php file is missing and the wp-content directory is not writable. Please ensure the wp-content folder is writable, then deactivate the plugin and try again.', 'sqlite-database-integration' )
);
return;
}
// The dropin db.php is missing.
printf(
'<div class="notice notice-error"><p>%s</p></div>',
sprintf(
/* translators: 1: db.php drop-in path, 2: Admin URL to deactivate the module */
__( 'The SQLite Integration plugin is active, but the %1$s file is missing. Please <a href="%2$s">deactivate the plugin</a> and re-activate it to try again.', 'sqlite-database-integration' ),
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php</code>',
esc_url( admin_url( 'plugins.php' ) )
)
);
}
add_action( 'admin_notices', 'sqlite_plugin_admin_notice' ); // Add the admin notices.
// Remove the PL-plugin admin notices for SQLite.
remove_action( 'admin_notices', 'perflab_sqlite_plugin_admin_notice' );
@@ -0,0 +1,174 @@
<?php
/**
* Functions for the admin page of the plugin.
*
* @since 1.0.0
* @package wp-sqlite-integration
*/
/**
* Add an admin menu page.
*
* @since 1.0.0
*/
function sqlite_add_admin_menu() {
add_options_page(
__( 'SQLite integration', 'sqlite-database-integration' ),
__( 'SQLite integration', 'sqlite-database-integration' ),
'manage_options',
'sqlite-integration',
'sqlite_integration_admin_screen'
);
}
add_action( 'admin_menu', 'sqlite_add_admin_menu' );
/**
* The admin page contents.
*/
function sqlite_integration_admin_screen() {
$db_dropin_path = WP_CONTENT_DIR . '/db.php';
/*
* When an existing "db.php" drop-in is detected, let's check if it's a known
* plugin that we can continue supporting even when we override the drop-in.
*/
$override_db_dropin = false;
if ( file_exists( $db_dropin_path ) && ! defined( 'SQLITE_DB_DROPIN_VERSION' ) ) {
// Check for the Query Monitor plugin.
// When "QM_DB" exists, it must have been loaded via the "db.php" file.
if ( class_exists( 'QM_DB', false ) ) {
$override_db_dropin = true;
}
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'SQLite integration.', 'sqlite-database-integration' ); ?></h1>
</div>
<!-- Set the wrapper width to 50em, to improve readability. -->
<div style="max-width:50em;">
<?php if ( defined( 'SQLITE_DB_DROPIN_VERSION' ) ) : ?>
<div class="notice notice-success">
<p><?php esc_html_e( 'SQLite is enabled.', 'sqlite-database-integration' ); ?></p>
</div>
<p>
<?php
printf(
/* translators: 1: Admin URL to deactivate the module, 2: db.php drop-in path, */
__( 'The SQLite drop-in is enabled. To disable it and get back to your previous, MySQL database, you can <a href="%1$s">deactivate the plugin</a>. Alternatively, you can manually delete the %2$s file from your server.', 'sqlite-database-integration' ),
esc_url( admin_url( 'plugins.php' ) ),
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php</code>'
);
?>
</p>
<?php elseif ( ! extension_loaded( 'pdo_sqlite' ) ) : ?>
<div class="notice notice-error">
<p><?php esc_html_e( 'We detected that the PDO SQLite driver is missing from your server (the pdo_sqlite extension is not loaded). Please make sure that SQLite is enabled in your PHP installation before proceeding.', 'sqlite-database-integration' ); ?></p>
</div>
<?php elseif ( file_exists( $db_dropin_path ) && ! defined( 'SQLITE_DB_DROPIN_VERSION' ) && ! $override_db_dropin ) : ?>
<?php if ( defined( 'PERFLAB_SQLITE_DB_DROPIN_VERSION' ) ) : ?>
<div class="notice notice-warning">
<p>
<?php
printf(
/* translators: %s: db.php drop-in path */
esc_html__( 'An older %s file was detected. Please click the button below to update the file.', 'sqlite-database-integration' ),
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php</code>'
);
?>
</p>
</div>
<a class="button button-primary" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=sqlite-integration&confirm-install&upgrade-from-pl' ), 'sqlite-install' ) ); ?>">
<?php
printf(
/* translators: %s: db.php drop-in path */
esc_html__( 'Update %s file', 'sqlite-database-integration' ),
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php</code>'
);
?>
</a>
<?php else : ?>
<div class="notice notice-error">
<p>
<?php
printf(
/* translators: %s: db.php drop-in path */
esc_html__( 'The SQLite plugin cannot be activated because a different %s drop-in already exists.', 'sqlite-database-integration' ),
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php</code>'
);
?>
</p>
</div>
<?php endif; ?>
<?php elseif ( ! is_writable( WP_CONTENT_DIR ) ) : ?>
<div class="notice notice-error">
<p>
<?php
printf(
/* translators: %s: db.php drop-in path */
esc_html__( 'The SQLite plugin cannot be activated because the %s directory is not writable.', 'sqlite-database-integration' ),
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '</code>'
);
?>
</p>
</div>
<?php else : ?>
<div class="notice notice-success">
<p><?php esc_html_e( 'All checks completed successfully, your site can use an SQLite database. You can proceed with the installation.', 'sqlite-database-integration' ); ?></p>
</div>
<h2><?php esc_html_e( 'Important note', 'sqlite-database-integration' ); ?></h2>
<p><?php esc_html_e( 'This plugin will switch to a separate database and install WordPress in it. You will need to reconfigure your site, and start with a fresh site. Disabling the plugin you will get back to your previous MySQL database, with all your previous data intact.', 'sqlite-database-integration' ); ?></p>
<?php if ( $override_db_dropin ) : ?>
<p>
<strong>NOTE:</strong>
<?php
printf(
/* translators: %s: db.php drop-in path */
esc_html__( 'Weve detected an existing database drop-in file at %s, created by the Query Monitor plugin.', 'sqlite-database-integration' ),
'<code>' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php</code>'
);
?>
<?php esc_html_e( 'To enable SQLite support, this file will need to be replaced.', 'sqlite-database-integration' ); ?>
<?php esc_html_e( 'The Query Monitor plugin will continue to function correctly after the change. You can safely proceed with the installation.', 'sqlite-database-integration' ); ?>
</p>
<?php endif; ?>
<p><?php esc_html_e( 'By clicking the button below, you will be redirected to the WordPress installation screen to setup your new database', 'sqlite-database-integration' ); ?></p>
<a class="button button-primary" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=sqlite-integration&confirm-install' ), 'sqlite-install' ) ); ?>"><?php esc_html_e( 'Install SQLite database', 'sqlite-database-integration' ); ?></a>
<?php endif; ?>
</div>
<?php
}
/**
* Adds a link to the admin bar.
*
* @since n.e.x.t
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param WP_Admin_Bar $admin_bar The admin bar object.
*/
function sqlite_plugin_adminbar_item( $admin_bar ) {
global $wpdb;
if ( defined( 'SQLITE_DB_DROPIN_VERSION' ) && defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ) {
$title = '<span style="color:#46B450;">' . __( 'Database: SQLite', 'sqlite-database-integration' ) . '</span>';
} elseif ( stripos( $wpdb->db_server_info(), 'maria' ) !== false ) {
$title = '<span style="color:#DC3232;">' . __( 'Database: MariaDB', 'sqlite-database-integration' ) . '</span>';
} else {
$title = '<span style="color:#DC3232;">' . __( 'Database: MySQL', 'sqlite-database-integration' ) . '</span>';
}
$args = array(
'id' => 'sqlite-db-integration',
'parent' => 'top-secondary',
'title' => $title,
'href' => esc_url( admin_url( 'options-general.php?page=sqlite-integration' ) ),
'meta' => false,
);
$admin_bar->add_node( $args );
}
add_action( 'admin_bar_menu', 'sqlite_plugin_adminbar_item', 999 );
@@ -0,0 +1,53 @@
<?php
/**
* Define constants for the SQLite implementation.
*
* @since 1.0.0
* @package wp-sqlite-integration
*/
// Temporary - This will be in wp-config.php once SQLite is merged in Core.
if ( ! defined( 'DB_ENGINE' ) ) {
if ( defined( 'SQLITE_DB_DROPIN_VERSION' ) ) {
define( 'DB_ENGINE', 'sqlite' );
} elseif ( defined( 'DATABASE_ENGINE' ) ) {
// backwards compatibility with previous versions of the plugin.
define( 'DB_ENGINE', DATABASE_ENGINE );
} else {
define( 'DB_ENGINE', 'mysql' );
}
}
/**
* Notice:
* Your scripts have the permission to create directories or files on your server.
* If you write in your wp-config.php like below, we take these definitions.
* define('DB_DIR', '/full_path_to_the_database_directory/');
* define('DB_FILE', 'database_file_name');
*/
/**
* FQDBDIR is a directory where the sqlite database file is placed.
* If DB_DIR is defined, it is used as FQDBDIR.
*/
if ( ! defined( 'FQDBDIR' ) ) {
if ( defined( 'DB_DIR' ) ) {
define( 'FQDBDIR', trailingslashit( DB_DIR ) );
} elseif ( defined( 'WP_CONTENT_DIR' ) ) {
define( 'FQDBDIR', WP_CONTENT_DIR . '/database/' );
} else {
define( 'FQDBDIR', ABSPATH . 'wp-content/database/' );
}
}
/**
* FQDB is a database file name. If DB_FILE is defined, it is used
* as FQDB.
*/
if ( ! defined( 'FQDB' ) ) {
if ( defined( 'DB_FILE' ) ) {
define( 'FQDB', FQDBDIR . DB_FILE );
} else {
define( 'FQDB', FQDBDIR . '.ht.sqlite' );
}
}
@@ -0,0 +1,61 @@
<?php
/**
* Plugin Name: SQLite integration (Drop-in)
* Version: 1.0.0
* Author: WordPress Performance Team
* Author URI: https://make.wordpress.org/performance/
*
* This file is auto-generated and copied from the sqlite plugin.
* Please don't edit this file directly.
*
* @package wp-sqlite-integration
*/
define( 'SQLITE_DB_DROPIN_VERSION', '1.8.0' );
// Tweak to allow copy-pasting the file without having to run string-replacements.
$sqlite_plugin_implementation_folder_path = '{SQLITE_IMPLEMENTATION_FOLDER_PATH}';
if ( ! file_exists( $sqlite_plugin_implementation_folder_path ) ) { // Check that the folder exists.
$sqlite_plugin_implementation_folder_path = realpath( __DIR__ . '/plugins/sqlite-database-integration' );
}
// Bail early if the SQLite implementation was not located in the plugin.
if ( ! $sqlite_plugin_implementation_folder_path || ! file_exists( $sqlite_plugin_implementation_folder_path . '/wp-includes/sqlite/db.php' ) ) {
return;
}
// Constant for backward compatibility.
if ( ! defined( 'DATABASE_TYPE' ) ) {
define( 'DATABASE_TYPE', 'sqlite' );
}
// Define SQLite constant.
if ( ! defined( 'DB_ENGINE' ) ) {
define( 'DB_ENGINE', 'sqlite' );
}
// Require the implementation from the plugin.
require_once $sqlite_plugin_implementation_folder_path . '/wp-includes/sqlite/db.php';
// Activate the performance-lab plugin if it is not already activated.
add_action(
'admin_footer',
function() {
if ( defined( 'SQLITE_MAIN_FILE' ) ) {
return;
}
if ( ! function_exists( 'activate_plugin' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_path = WP_PLUGIN_DIR . '/' . '{SQLITE_PLUGIN}';
if ( file_exists( $plugin_path ) && is_plugin_inactive( '{SQLITE_PLUGIN}' ) ) {
// If `activate_plugin()` returns a value other than null (like WP_Error),
// the plugin could not be found. Try with a hardcoded string,
// because that probably means the file was directly copy-pasted.
if ( null !== activate_plugin( '{SQLITE_PLUGIN}', '', false, true ) ) {
activate_plugin( 'sqlite-database-integration/load.php', '', false, true );
}
}
}
);
@@ -0,0 +1,68 @@
<?php
/**
* Handle the SQLite deactivation.
*
* @since 1.0.0
* @package wp-sqlite-integration
*/
/**
* Delete the db.php file in wp-content.
*
* When the plugin gets merged in wp-core, this is not to be ported.
*/
function sqlite_plugin_remove_db_file() {
if ( ! defined( 'SQLITE_DB_DROPIN_VERSION' ) || ! file_exists( WP_CONTENT_DIR . '/db.php' ) ) {
return;
}
global $wp_filesystem;
require_once ABSPATH . '/wp-admin/includes/file.php';
// Init the filesystem if needed, then delete custom drop-in.
if ( $wp_filesystem || WP_Filesystem() ) {
// Flush any persistent cache.
wp_cache_flush();
// Delete the drop-in.
$wp_filesystem->delete( WP_CONTENT_DIR . '/db.php' );
// Flush the cache again to mitigate a possible race condition.
wp_cache_flush();
}
// Run an action on `shutdown`, to deactivate the option in the MySQL database.
add_action(
'shutdown',
function () {
global $table_prefix;
// Get credentials for the MySQL database.
$dbuser = defined( 'DB_USER' ) ? DB_USER : '';
$dbpassword = defined( 'DB_PASSWORD' ) ? DB_PASSWORD : '';
$dbname = defined( 'DB_NAME' ) ? DB_NAME : '';
$dbhost = defined( 'DB_HOST' ) ? DB_HOST : '';
// Init a connection to the MySQL database.
$wpdb_mysql = new wpdb( $dbuser, $dbpassword, $dbname, $dbhost );
$wpdb_mysql->set_prefix( $table_prefix );
// Get the perflab options, remove the database/sqlite module and update the option.
$row = $wpdb_mysql->get_row( $wpdb_mysql->prepare( "SELECT option_value FROM $wpdb_mysql->options WHERE option_name = %s LIMIT 1", 'active_plugins' ) );
if ( is_object( $row ) ) {
$value = maybe_unserialize( $row->option_value );
if ( is_array( $value ) ) {
$value_flipped = array_flip( $value );
$items = array_reverse( explode( DIRECTORY_SEPARATOR, SQLITE_MAIN_FILE ) );
$item = $items[1] . DIRECTORY_SEPARATOR . $items[0];
unset( $value_flipped[ $item ] );
$value = array_flip( $value_flipped );
$wpdb_mysql->update( $wpdb_mysql->options, array( 'option_value' => maybe_serialize( $value ) ), array( 'option_name' => 'active_plugins' ) );
}
}
},
PHP_INT_MAX
);
// Flush any persistent cache.
wp_cache_flush();
}
register_deactivation_hook( SQLITE_MAIN_FILE, 'sqlite_plugin_remove_db_file' ); // Remove db.php file on plugin deactivation.
@@ -0,0 +1,83 @@
<?php
/**
* Tweaks for the health-check screens.
*
* @since 1.0.0
* @package wp-sqlite-integration
*/
/**
* Filter debug data in site-health screen.
*
* When the plugin gets merged in wp-core, these should be merged in src/wp-admin/includes/class-wp-debug-data.php
* See https://github.com/WordPress/wordpress-develop/pull/3220/files
*
* @param array $info The debug data.
*/
function sqlite_plugin_filter_debug_data( $info ) {
$db_engine = defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ? 'sqlite' : 'mysql';
$info['wp-constants']['fields']['DB_ENGINE'] = array(
'label' => 'DB_ENGINE',
'value' => ( defined( 'DB_ENGINE' ) ? DB_ENGINE : __( 'Undefined', 'sqlite-database-integration' ) ),
'debug' => ( defined( 'DB_ENGINE' ) ? DB_ENGINE : 'undefined' ),
);
$info['wp-database']['fields']['db_engine'] = array(
'label' => __( 'Database type', 'sqlite-database-integration' ),
'value' => 'sqlite' === $db_engine ? 'SQLite' : 'MySQL/MariaDB',
);
if ( 'sqlite' === $db_engine ) {
$info['wp-database']['fields']['database_version'] = array(
'label' => __( 'SQLite version', 'sqlite-database-integration' ),
'value' => $info['wp-database']['fields']['server_version'] ?? null,
);
$info['wp-database']['fields']['database_file'] = array(
'label' => __( 'Database file', 'sqlite-database-integration' ),
'value' => FQDB,
'private' => true,
);
$info['wp-database']['fields']['database_size'] = array(
'label' => __( 'Database size', 'sqlite-database-integration' ),
'value' => size_format( filesize( FQDB ) ),
);
unset( $info['wp-database']['fields']['extension'] );
unset( $info['wp-database']['fields']['server_version'] );
unset( $info['wp-database']['fields']['client_version'] );
unset( $info['wp-database']['fields']['database_host'] );
unset( $info['wp-database']['fields']['database_user'] );
unset( $info['wp-database']['fields']['database_name'] );
unset( $info['wp-database']['fields']['database_charset'] );
unset( $info['wp-database']['fields']['database_collate'] );
unset( $info['wp-database']['fields']['max_allowed_packet'] );
unset( $info['wp-database']['fields']['max_connections'] );
}
return $info;
}
add_filter( 'debug_information', 'sqlite_plugin_filter_debug_data' ); // Filter debug data in site-health screen.
/**
* Filter site_status tests in site-health screen.
*
* When the plugin gets merged in wp-core, these should be merged in src/wp-admin/includes/class-wp-site-health.php
*
* @param array $tests The tests.
* @return array
*/
function sqlite_plugin_filter_site_status_tests( $tests ) {
$db_engine = defined( 'DB_ENGINE' ) && 'sqlite' === DB_ENGINE ? 'sqlite' : 'mysql';
if ( 'sqlite' === $db_engine ) {
unset( $tests['direct']['utf8mb4_support'] );
unset( $tests['direct']['sql_server'] );
unset( $tests['direct']['persistent_object_cache'] ); // Throws an error because DB_NAME is not defined.
}
return $tests;
}
add_filter( 'site_status_tests', 'sqlite_plugin_filter_site_status_tests' );
@@ -0,0 +1,172 @@
<?php
/**
* Boot Query Monitor from the SQLite Database Integration plugin.
*
* When the Query Monitor plugin exists in its standard location, let's check
* if it is active, so we can boot it eagerly. This is a workaround to avoid
* SQLite and Query Monitor competing for the "wp-content/db.php" file.
*
* This file is a modified version of the original Query Monitor "db.php" file.
*
* See: https://github.com/johnbillion/query-monitor/blob/develop/wp-content/db.php
*/
/*
* In Playground, the SQLite plugin is preloaded without using the "db.php" file.
* To prevent Query Monitor from injecting its own "db.php" file, we need to set
* the "QM_DB_SYMLINK" constant to "false".
*/
if ( ! defined( 'QM_DB_SYMLINK' ) ) {
define( 'QM_DB_SYMLINK', false );
}
// Check if we should load Query Monitor (as per the original "db.php" file).
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! defined( 'DB_USER' ) ) {
return;
}
if ( defined( 'QM_DISABLED' ) && QM_DISABLED ) {
return;
}
if ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) {
return;
}
if ( 'cli' === php_sapi_name() && ! defined( 'QM_TESTS' ) ) {
return;
}
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
return;
}
if ( is_admin() ) {
if ( isset( $_GET['action'] ) && 'upgrade-plugin' === $_GET['action'] ) {
return;
}
if ( isset( $_POST['action'] ) && 'update-plugin' === $_POST['action'] ) {
return;
}
}
/*
* Register SQLite enhancements for Query Monitor when plugins are loaded.
*
* This will also ensure that the plugin Query Monitor is fully initialized even
* when we can't load it eagerly, e.g. on a multisite install.
*/
function register_sqlite_enhancements_for_query_monitor() {
if ( ! class_exists( 'QM_Backtrace' ) ) {
return;
}
if ( defined( 'QM_VERSION' ) && version_compare( QM_VERSION, '4.0.0', '>=' ) ) {
require_once __DIR__ . '/qm4.php';
} else {
require_once __DIR__ . '/qm3.php';
}
if ( ! defined( 'SQLITE_QUERY_MONITOR_LOADED' ) ) {
define( 'SQLITE_QUERY_MONITOR_LOADED', true );
}
}
if ( function_exists( 'add_action' ) ) {
add_action( 'plugins_loaded', 'register_sqlite_enhancements_for_query_monitor' );
}
/*
* On a multisite install, we can't easily determine the current site eagerly.
* Therefore, let's bail out and let Query Monitor activate later as a plugin.
*/
if ( is_multisite() ) {
return;
}
/*
* Now, let's try to load Query Monitor eagerly, to start logging queries early.
* When the plugin is installed, we will check the database if it is also active.
*/
global $wpdb;
if ( ! isset( $wpdb ) ) {
return;
}
// Check if Query Monitor is installed.
if ( defined( 'WP_PLUGIN_DIR' ) ) {
$plugins_dir = WP_PLUGIN_DIR;
} else {
$plugins_dir = WP_CONTENT_DIR . '/plugins';
}
$qm_dir = "{$plugins_dir}/query-monitor";
$qm_php = "{$qm_dir}/classes/PHP.php";
if ( ! is_readable( $qm_php ) ) {
return;
}
// Check if Query Monitor is active.
if ( null === $wpdb->options ) {
global $table_prefix;
$wpdb->set_prefix( $table_prefix ?? '' );
}
$query_monitor_active = false;
try {
// Make sure no errors are displayed when the query fails.
$show_errors = $wpdb->hide_errors();
$value = $wpdb->get_row(
$wpdb->prepare(
"SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
'active_plugins'
)
);
$wpdb->show_errors( $show_errors );
if ( null !== $value ) {
$query_monitor_active = in_array(
'query-monitor/query-monitor.php',
unserialize( $value->option_value ),
true
);
}
} catch ( Throwable $e ) {
return;
}
if ( ! $query_monitor_active ) {
return;
}
// Load Query Monitor eagerly (as per the original "db.php" file).
require_once $qm_php;
if ( ! QM_PHP::version_met() ) {
return;
}
if ( ! file_exists( "{$qm_dir}/vendor/autoload.php" ) ) {
add_action( 'all_admin_notices', 'QM_PHP::vendor_nope' );
return;
}
require_once "{$qm_dir}/vendor/autoload.php";
if ( ! class_exists( 'QM_Backtrace' ) ) {
return;
}
if ( ! defined( 'SAVEQUERIES' ) ) {
define( 'SAVEQUERIES', true );
}
// Mark the Query Monitor integration as loaded.
define( 'SQLITE_QUERY_MONITOR_LOADED', true );
@@ -0,0 +1,89 @@
<?php // phpcs:disable WordPress.Files.FileName.InvalidClassFileName
if ( ! class_exists( 'QM_Output_Html_DB_Queries' ) || ! class_exists( 'QM_Collectors' ) ) {
return;
}
/**
* Override Query Monitor's "QM_Output_Html_DB_Queries" to inject SQLite info.
*/
class SQLite_QM_Output_Html_DB_Queries extends QM_Output_Html_DB_Queries {
/**
* Override the parent method to inject SQLite info.
*
* Currently, Query Monitor doesn't provide a way to customize the rendered
* query HTML. To overcome this limitation, we capture the HTML generated by
* the parent method and modify it to inject the SQLite query data.
*
* @param array<string, mixed> $row The row data.
* @param array<int, string> $cols The column names.
* @return void
*/
protected function output_query_row( array $row, array $cols ) {
// Capture the query row HTML.
ob_start();
parent::output_query_row( $row, $cols );
$data = ob_get_length() > 0 ? ob_get_clean() : '';
// Get the corresponding SQLite queries.
global $wpdb;
static $query_index = 0;
$sqlite_queries = $wpdb->queries[ $query_index ]['sqlite_queries'] ?? array();
$sqlite_query_count = count( $sqlite_queries );
$query_index += 1;
// Build the SQLite info HTML.
$sqlite_info = sprintf(
'<div class="qm-info" style="margin: 15px 0 8px;">Executed %d SQLite %s:</div>',
$sqlite_query_count,
1 === $sqlite_query_count ? 'Query' : 'Queries'
);
$sqlite_info .= '<ol>';
foreach ( $sqlite_queries as $query ) {
$sqlite_info .= '<li class="qm-sqlite-query" style="list-style: decimal !important; margin-left: 20px !important;">';
$sqlite_info .= '<code>' . str_replace( '<br>', '', self::format_sql( $query['sql'] ) ) . '</code>';
$sqlite_info .= '</li>';
}
$sqlite_info .= '</ol>';
// Inject toggle button and SQLite info into the query row HTML.
$toggle_button = '<button class="qm-toggle sqlite-toggle" data-on="+" data-off="-" aria-expanded="false" aria-label="Toggle SQLite queries" title="Toggle SQLite queries"><span aria-hidden="true">+</span></button>';
$toggle_content = sprintf( '<div class="qm-toggled" style="display: none;">%s</div>', $sqlite_info );
$data = str_replace( 'qm-row-sql', 'qm-row-sql qm-has-toggle', $data );
$data = preg_replace(
'/(<td class="qm-row-sql.*?">)(.*?)(<\/td>)/s',
implode(
array(
'$1',
str_replace( '$', '\\$', $toggle_button ),
'$2',
str_replace( '$', '\\$', $toggle_content ),
'$3',
)
),
$data
);
echo $data;
}
}
// Remove the default Query Monitor class and replace it with the custom one.
remove_filter( 'qm/outputter/html', 'register_qm_output_html_db_queries', 20 );
/**
* Register the custom HTML output class.
*
* @param array<string, QM_Output> $output
* @param QM_Collectors $collectors
* @return array<string, QM_Output>
*/
function register_sqlite_qm_output_html_db_queries( array $output, $collectors ) {
$collector = QM_Collectors::get( 'db_queries' );
if ( $collector ) {
$output['db_queries'] = new SQLite_QM_Output_Html_DB_Queries( $collector );
}
return $output;
}
add_filter( 'qm/outputter/html', 'register_sqlite_qm_output_html_db_queries', 20, 2 );
@@ -0,0 +1,107 @@
<?php // phpcs:disable WordPress.Files.FileName.InvalidClassFileName, Generic.Files.OneObjectStructurePerFile.MultipleFound
if ( ! class_exists( 'QM_Collector' ) ) {
return;
}
/**
* Data transfer object for SQLite query data.
*/
class SQLite_QM_Data extends QM_Data {
/**
* SQLite queries indexed by normalized MySQL SQL text.
*
* @var array<string, list<string>>
*/
public $queries = array();
}
/**
* Collector for SQLite query data.
*
* Extracts SQLite queries from $wpdb->queries and stores them
* indexed by SQL text for the QM 4.0+ JS integration.
*/
class SQLite_QM_Collector extends QM_Collector {
/** @var string */
public $id = 'sqlite';
public function get_storage(): QM_Data {
return new SQLite_QM_Data();
}
public function process(): void {
global $wpdb;
if ( empty( $wpdb->queries ) ) {
return;
}
// Index by SQL rather than row position — robust to filtering, sorting, etc.
$mapped = array();
foreach ( $wpdb->queries as $query ) {
// Query Monitor skips queries with 'wp_admin_bar' in the stack.
if ( false !== strpos( $query[2] ?? '', 'wp_admin_bar' ) ) {
continue;
}
if ( ! empty( $query['sqlite_queries'] ) ) {
$sql = trim( preg_replace( '/\s+/', ' ', $query[0] ) );
$mapped[ $sql ] = array_column( $query['sqlite_queries'], 'sql' );
}
}
$this->data->queries = $mapped;
}
}
/**
* HTML outputter for SQLite query data.
*
* With $client_side_rendered = true, QM auto-serializes the collector data
* into "window.QueryMonitorData.data.sqlite". This outputter's only job is to
* emit the inline JS module that reads that data and injects SQLite query
* details into QM 4.0's shadow DOM DB queries panel.
*/
class SQLite_QM_Output_Html extends QM_Output_Html {
/** @var bool */
public static $client_side_rendered = true;
public function name(): string {
return 'SQLite';
}
public function output(): void {
if ( empty( $this->get_collector()->get_data()->queries ) ) {
return;
}
$js_path = __DIR__ . '/query-monitor-sqlite.js';
if ( is_readable( $js_path ) ) {
echo '<script type="module">';
include $js_path;
echo '</script>';
}
}
}
/**
* Register the SQLite collector.
*/
function register_sqlite_qm_collector( array $collectors ): array {
$collectors['sqlite'] = new SQLite_QM_Collector();
return $collectors;
}
add_filter( 'qm/collectors', 'register_sqlite_qm_collector', 20 );
/**
* Register the SQLite HTML outputter.
*/
function register_sqlite_qm_output_html( array $output, QM_Collectors $collectors ): array {
$collector = QM_Collectors::get( 'sqlite' );
if ( $collector ) {
$output['sqlite'] = new SQLite_QM_Output_Html( $collector );
}
return $output;
}
add_filter( 'qm/outputter/html', 'register_sqlite_qm_output_html', 30, 2 );
@@ -0,0 +1,95 @@
const STYLE = `
details.qm-sqlite {
margin: 6px 0 0;
}
details.qm-sqlite summary {
cursor: pointer;
}
details.qm-sqlite ol {
margin: 6px 0 0;
padding-left: 24px;
list-style: decimal;
}
`;
const container = document.getElementById( 'query-monitor-container' );
const sqliteData = window.QueryMonitorData?.data?.sqlite?.data?.queries;
if ( container && sqliteData ) {
// QM attaches the shadow root in its own DOMContentLoaded listener.
// Our module is loaded after QM's, so our listener fires after QM's.
document.addEventListener( 'DOMContentLoaded', () => {
const shadowRoot = container.shadowRoot;
if ( ! shadowRoot ) {
return;
}
inject( shadowRoot, sqliteData );
// Re-inject after Preact re-renders (panel switches, filters, etc.).
// Debounced to avoid excessive work during rapid DOM updates.
let timer;
new MutationObserver( () => {
clearTimeout( timer );
timer = setTimeout( () => inject( shadowRoot, sqliteData ), 100 );
} ).observe( shadowRoot, { childList: true, subtree: true } );
} );
}
function inject( shadowRoot, data ) {
const panel = shadowRoot.getElementById( 'qm-db_queries' );
if ( ! panel ) {
return;
}
if ( ! shadowRoot.querySelector( 'style.qm-sqlite-style' ) ) {
const style = document.createElement( 'style' );
style.className = 'qm-sqlite-style';
style.textContent = STYLE;
shadowRoot.appendChild( style );
}
// Match by SQL rather than row position — robust to filtering, sorting, etc.
for ( const code of panel.querySelectorAll( 'td.qm-cell-sql > code' ) ) {
const cell = code.parentElement;
const key = code.innerText.replace( /\s+/g, ' ' ).trim();
const queries = data[ key ];
const existing = cell.querySelector( 'details.qm-sqlite' );
// Preact may recycle DOM nodes on filter/sort, leaving stale details
// from a previous query. Remove them when the SQL key no longer matches.
if ( existing ) {
if ( queries?.length && existing.dataset.sqliteKey === key ) {
continue;
}
existing.remove();
}
if ( queries?.length ) {
cell.append( buildDetails( key, queries ) );
}
}
}
function buildDetails( key, queries ) {
const details = document.createElement( 'details' );
details.className = 'qm-sqlite';
details.dataset.sqliteKey = key;
// Prevent QM's row click handlers from firing when toggling.
details.addEventListener( 'click', ( e ) => e.stopPropagation() );
const summary = document.createElement( 'summary' );
summary.textContent = `Executed ${ queries.length } SQLite ${ queries.length === 1 ? 'Query' : 'Queries' }`;
const ol = document.createElement( 'ol' );
for ( const sql of queries ) {
const li = document.createElement( 'li' );
li.className = 'qm-sqlite-query';
const code = document.createElement( 'code' );
code.textContent = sql;
li.append( code );
ol.append( li );
}
details.append( summary, ol );
return details;
}
@@ -0,0 +1,27 @@
<?php
/**
* Plugin Name: SQLite Database Integration
* Description: SQLite database driver drop-in.
* Author: The WordPress Team
* Version: 3.0.0-rc.4
* Requires PHP: 7.2
* Textdomain: sqlite-database-integration
*
* This feature plugin allows WordPress to use SQLite instead of MySQL as its database.
*
* @package wp-sqlite-integration
*/
/**
* Load the "SQLITE_DRIVER_VERSION" constant.
* This constant needs to be updated on plugin release!
*/
require_once __DIR__ . '/wp-includes/database/version.php';
define( 'SQLITE_MAIN_FILE', __FILE__ );
require_once __DIR__ . '/admin-page.php';
require_once __DIR__ . '/activate.php';
require_once __DIR__ . '/deactivate.php';
require_once __DIR__ . '/admin-notices.php';
require_once __DIR__ . '/health-check.php';
@@ -0,0 +1,90 @@
=== SQLite Database Integration ===
Contributors: wordpressdotorg, aristath, janjakes, zieladam, berislav.grgicak, bpayton, zaerl
Requires at least: 6.4
Tested up to: 6.9
Requires PHP: 7.2
Stable tag: 3.0.0-rc.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Tags: performance, database
SQLite integration plugin by the WordPress Team.
== Description ==
The SQLite plugin is a community, feature plugin. The intent is to allow testing an SQLite integration with WordPress and gather feedback, with the goal of eventually landing it in WordPress core.
== Frequently Asked Questions ==
= What is the purpose of this plugin? =
The primary purpose of the SQLite plugin is to allow testing the use of an SQLite database, with the goal to eventually land in WordPress core.
You can read the original proposal on the [Make blog](https://make.wordpress.org/core/2022/09/12/lets-make-wordpress-officially-support-sqlite/), as well as the [call for testing](https://make.wordpress.org/core/2022/12/20/help-us-test-the-sqlite-implementation/) for more context and useful information.
= Can I use this plugin on my production site? =
Per the primary purpose of the plugin (see above), it can mostly be considered a beta testing plugin. To a degree, it should be okay to use it in production. However, as with every plugin, you are doing so at your own risk.
= Where can I submit my plugin feedback? =
Feedback is encouraged and much appreciated, especially since this plugin is a future WordPress core feature. If you need help with troubleshooting or have a question, suggestions, or requests, you can [submit them as an issue in the SQLite GitHub repository](https://github.com/wordpress/sqlite-database-integration/issues/new).
= How can I contribute to the plugin? =
Contributions are always welcome! Learn more about how to get involved in the [Core Performance Team Handbook](https://make.wordpress.org/performance/handbook/get-involved/).
= Does this plugin change how WordPress queries are executed? =
The plugin replaces the default MySQL-based database layer with an
SQLite-backed implementation. Core WordPress code continues to use
the wpdb API, while queries are internally adapted to be compatible
with SQLite syntax and behavior.
== Changelog ==
= 3.0.0-rc.4 =
* Normalize BIT column default literals ([#439](https://github.com/WordPress/sqlite-database-integration/pull/439))
* Optimize MySQL lexer (~2× speedup) ([#424](https://github.com/WordPress/sqlite-database-integration/pull/424))
* Fix `RAND()` function behavior ([#363](https://github.com/WordPress/sqlite-database-integration/pull/363))
* CI: Disable Xdebug, use Rust release builds, consolidate unit-test matrix ([#425](https://github.com/WordPress/sqlite-database-integration/pull/425))
* GitHub Actions workflow updates ([#404](https://github.com/WordPress/sqlite-database-integration/pull/404))
* Add native Rust-based MySQL parser extension ([#381](https://github.com/WordPress/sqlite-database-integration/pull/381), [#384](https://github.com/WordPress/sqlite-database-integration/pull/384), [#386](https://github.com/WordPress/sqlite-database-integration/pull/386), [#389](https://github.com/WordPress/sqlite-database-integration/pull/389), [#390](https://github.com/WordPress/sqlite-database-integration/pull/390), [#394](https://github.com/WordPress/sqlite-database-integration/pull/394), [#398](https://github.com/WordPress/sqlite-database-integration/pull/398))
* Ship native parser as WASM ([#395](https://github.com/WordPress/sqlite-database-integration/pull/395), [#396](https://github.com/WordPress/sqlite-database-integration/pull/396), [#397](https://github.com/WordPress/sqlite-database-integration/pull/397), [#399](https://github.com/WordPress/sqlite-database-integration/pull/399), [#400](https://github.com/WordPress/sqlite-database-integration/pull/400), [#401](https://github.com/WordPress/sqlite-database-integration/pull/401))
* Add SQLite plugin landing page ([#407](https://github.com/WordPress/sqlite-database-integration/pull/407), [#412](https://github.com/WordPress/sqlite-database-integration/pull/412))
* Check Playground web runtime compatibility ([#419](https://github.com/WordPress/sqlite-database-integration/pull/419))
= 3.0.0-rc.3 =
* Lexer: Fix possible OOB read in quoted strings ([#374](https://github.com/WordPress/sqlite-database-integration/pull/374))
* Add support for `NO_AUTO_VALUE_ON_ZERO` SQL mode ([#366](https://github.com/WordPress/sqlite-database-integration/pull/366))
= 3.0.0-rc.2 =
* Support MySQL `BINARY` operator ([#369](https://github.com/WordPress/sqlite-database-integration/pull/369))
* Add support for `AUTO_INCREMENT` value management ([#367](https://github.com/WordPress/sqlite-database-integration/pull/367))
* Add support for `DELETE` with `LIMIT` and `ORDER BY` ([#365](https://github.com/WordPress/sqlite-database-integration/pull/365))
= 3.0.0-rc.1 =
* Improve concurrent database access ([#361](https://github.com/WordPress/sqlite-database-integration/pull/361))
* Remove legacy SQLite driver ([#358](https://github.com/WordPress/sqlite-database-integration/pull/358))
= 2.2.23 =
* Add Query Monitor 4.0 support ([#357](https://github.com/WordPress/sqlite-database-integration/pull/357))
* Translate MySQL CONVERT() expressions to SQLite ([#356](https://github.com/WordPress/sqlite-database-integration/pull/356))
= 2.2.22 =
* Support INSERT without INTO keyword ([#354](https://github.com/WordPress/sqlite-database-integration/pull/354))
* Add tests for MySQL row-level locking clauses ([#342](https://github.com/WordPress/sqlite-database-integration/pull/342))
* Improve automated deploy setup.
= 2.2.21 =
* Monorepo setup + release automation ([#334](https://github.com/WordPress/sqlite-database-integration/pull/334))
* Rework release workflow ([#350](https://github.com/WordPress/sqlite-database-integration/pull/350))
* Fix incorrect PHP polyfill implementations ([#338](https://github.com/WordPress/sqlite-database-integration/pull/338))
@@ -0,0 +1,45 @@
<?php
define( 'WP_MYSQL_ON_SQLITE_LOADER_PATH', __FILE__ );
/**
* Load the PDO MySQL-on-SQLite driver and its dependencies.
*/
require_once __DIR__ . '/php-polyfills.php';
require_once __DIR__ . '/version.php';
require_once __DIR__ . '/parser/class-wp-parser-grammar.php';
require_once __DIR__ . '/parser/class-wp-parser.php';
require_once __DIR__ . '/parser/class-wp-parser-node.php';
require_once __DIR__ . '/parser/class-wp-parser-token.php';
require_once __DIR__ . '/mysql/class-wp-mysql-token.php';
/*
* The MySQL lexer and parser have an optional native (e.g. Rust) implementation.
* When the native extension is loaded, it pre-declares WP_MySQL_Native_Lexer /
* WP_MySQL_Native_Parser; otherwise we fall back to the pure-PHP classes shipped
* here. WP_MySQL_Lexer / WP_MySQL_Parser is the public entrypoint either way.
*/
if ( class_exists( 'WP_MySQL_Native_Lexer', false ) ) {
require_once __DIR__ . '/mysql/native/class-wp-mysql-lexer.php';
} else {
require_once __DIR__ . '/mysql/class-wp-mysql-lexer.php';
}
if ( class_exists( 'WP_MySQL_Native_Parser', false ) ) {
require_once __DIR__ . '/mysql/native/mysql-rust-bridge.php';
require_once __DIR__ . '/mysql/native/class-wp-mysql-native-parser-node.php';
require_once __DIR__ . '/mysql/native/trait-wp-mysql-native-parser-impl.php';
require_once __DIR__ . '/mysql/native/class-wp-mysql-parser.php';
} else {
require_once __DIR__ . '/mysql/class-wp-mysql-parser.php';
}
require_once __DIR__ . '/sqlite/class-wp-sqlite-connection.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-configurator.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-driver.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-driver-exception.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-information-schema-builder.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-information-schema-exception.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-information-schema-reconstructor.php';
require_once __DIR__ . '/sqlite/class-wp-sqlite-pdo-user-defined-functions.php';
require_once __DIR__ . '/sqlite/class-wp-pdo-mysql-on-sqlite.php';
require_once __DIR__ . '/sqlite/class-wp-pdo-proxy-statement.php';
@@ -0,0 +1,63 @@
<?php
class WP_MySQL_Parser extends WP_Parser {
/**
* The current query AST.
*
* @var WP_Parser_Node|null
*/
private $current_ast;
/**
* Reset this parser with a new token stream.
*
* @param array<WP_Parser_Token> $tokens The parser tokens.
*/
public function reset_tokens( array $tokens ): void {
$this->tokens = $tokens;
$this->position = 0;
$this->current_ast = null;
}
/**
* Parse the next query from the input SQL string.
*
* This method reads tokens until a query is parsed, or the parsing fails.
* It returns a boolean indicating whether a query was successfully parsed.
*
* Example:
*
* // Parse all queries in the input SQL string.
* $parser = new WP_MySQL_Parser( $sql );
* while ( $parser->next_query() ) {
* $ast = $parser->get_query_ast();
* if ( ! $ast ) {
* // The parsing failed.
* }
* // The query was successfully parsed.
* }
*
* @return bool Whether a query was successfully parsed.
*/
public function next_query(): bool {
if ( $this->position >= count( $this->tokens ) ) {
return false;
}
$this->current_ast = $this->parse();
return true;
}
/**
* Get the current query AST.
*
* When no query has been parsed yet, the parsing failed, or the end of the
* input was reached, this method returns null.
*
* @see WP_MySQL_Parser::next_query() for usage example.
*
* @return WP_Parser_Node|null The current query AST, or null if no query was parsed.
*/
public function get_query_ast(): ?WP_Parser_Node {
return $this->current_ast;
}
}
@@ -0,0 +1,187 @@
<?php
/**
* MySQL token.
*
* This class represents a MySQL SQL token that is produced by WP_MySQL_Lexer,
* and consumed by WP_MySQL_Parser during the parsing process.
*/
class WP_MySQL_Token extends WP_Parser_Token {
/**
* Whether the NO_BACKSLASH_ESCAPES SQL mode is enabled.
*
* @var bool
*/
private $sql_mode_no_backslash_escapes_enabled;
/**
* Constructor.
*
* @param int $id Token type.
* @param int $start Byte offset in the input where the token begins.
* @param int $length Byte length of the token in the input.
* @param string $input Input bytes from which the token was parsed.
* @param bool $sql_mode_no_backslash_escapes_enabled Whether the NO_BACKSLASH_ESCAPES SQL mode is enabled.
*/
public function __construct(
int $id,
int $start,
int $length,
string $input,
bool $sql_mode_no_backslash_escapes_enabled
) {
$this->id = $id;
$this->start = $start;
$this->length = $length;
$this->input = $input;
$this->sql_mode_no_backslash_escapes_enabled = $sql_mode_no_backslash_escapes_enabled;
}
/**
* Get the name of the token.
*
* This method is intended to be used only for testing and debugging purposes,
* when tokens need to be presented by their names in a human-readable form.
* It should not be used in production code, as it's not performance-optimized.
*
* @return string The token name.
*/
public function get_name(): string {
$name = WP_MySQL_Lexer::get_token_name( $this->id );
if ( null === $name ) {
$name = 'UNKNOWN';
}
return $name;
}
/**
* Get the real unquoted value of the token.
*
* @return string The token value.
*/
public function get_value(): string {
$value = $this->get_bytes();
if (
WP_MySQL_Lexer::SINGLE_QUOTED_TEXT === $this->id
|| WP_MySQL_Lexer::DOUBLE_QUOTED_TEXT === $this->id
|| WP_MySQL_Lexer::BACK_TICK_QUOTED_ID === $this->id
) {
// Remove bounding quotes.
$quote = $value[0];
$value = substr( $value, 1, -1 );
/*
* When the NO_BACKSLASH_ESCAPES SQL mode is enabled, we only need to
* handle escaped bounding quotes, as the other characters preserve
* their literal values.
*/
if ( $this->sql_mode_no_backslash_escapes_enabled ) {
return str_replace( $quote . $quote, $quote, $value );
}
/**
* Unescape MySQL escape sequences.
*
* MySQL string literals use backslash as an escape character, and
* the string bounding quotes can also be escaped by being doubled.
*
* The escaping is done according to the following rules:
*
* 1. Some special character escape sequences are recognized.
* For example, "\n" is a newline character, "\0" is ASCII NULL.
* 2. A specific treatment is applied to "\%" and "\_" sequences.
* This is due to their special meaning for pattern matching.
* 3. Other backslash-prefixed characters resolve to their literal
* values. For example, "\x" represents "x", "\\" represents "\".
*
* Despite looking similar, these rules are different from the C-style
* string escaping, so we cannot use "strip(c)slashes()" in this case.
*
* See: https://dev.mysql.com/doc/refman/8.4/en/string-literals.html
*/
$backslash = chr( 92 );
$replacements = array(
/*
* MySQL special character escape sequences.
*/
( $backslash . '0' ) => chr( 0 ), // An ASCII NULL character (\0).
( $backslash . "'" ) => chr( 39 ), // A single quote character (').
( $backslash . '"' ) => chr( 34 ), // A double quote character (").
( $backslash . 'b' ) => chr( 8 ), // A backspace character.
( $backslash . 'n' ) => chr( 10 ), // A newline (linefeed) character (\n).
( $backslash . 'r' ) => chr( 13 ), // A carriage return character (\r).
( $backslash . 't' ) => chr( 9 ), // A tab character (\t).
( $backslash . 'Z' ) => chr( 26 ), // An ASCII 26 (Control+Z) character.
/*
* Normalize escaping of "%" and "_" characters.
*
* MySQL has unusual handling for "\%" and "\_" in all string literals.
* While other sequences follow the C-style escaping ("\?" is "?", etc.),
* "\%" resolves to "\%" and "\_" resolves to "\_" (unlike in C strings).
*
* This means that "\%" behaves like "\\%", and "\_" behaves like "\\_".
* To preserve this behavior, we need to add a second backslash here.
*
* From https://dev.mysql.com/doc/refman/8.4/en/string-literals.html:
* > The \% and \_ sequences are used to search for literal instances
* > of % and _ in pattern-matching contexts where they would otherwise
* > be interpreted as wildcard characters. If you use \% or \_ outside
* > of pattern-matching contexts, they evaluate to the strings \% and
* > \_, not to % and _.
*/
( $backslash . '%' ) => $backslash . $backslash . '%',
( $backslash . '_' ) => $backslash . $backslash . '_',
/*
* Preserve a double backslash as-is, so that the trailing backslash
* is not consumed as the beginning of an escape sequence like "\n".
*
* Resolving "\\" to "\" will be handled in the next step, where all
* other backslash-prefixed characters resolve to their literal values.
*/
( $backslash . $backslash )
=> $backslash . $backslash,
/*
* The bounding quotes can also be escaped by being doubled.
*/
( $quote . $quote ) => $quote,
);
/*
* Apply the replacements.
*
* It is important to use "strtr()" and not "str_replace()", because
* "str_replace()" applies replacements one after another, modifying
* intermediate changes rather than just the original string:
*
* - str_replace( [ 'a', 'b' ], [ 'b', 'c' ], 'ab' ); // 'cc' (bad)
* - strtr( 'ab', [ 'a' => 'b', 'b' => 'c' ] ); // 'bc' (good)
*/
$value = strtr( $value, $replacements );
/*
* A backslash with any other character represents the character itself.
* That is, \x evaluates to x, \\ evaluates to \, and \🙂 evaluates to 🙂.
*/
$preg_quoted_backslash = preg_quote( $backslash );
$value = preg_replace( "/$preg_quoted_backslash(.)/u", '$1', $value );
}
return $value;
}
/**
* Get the token representation as a string.
*
* This method is intended to be used only for testing and debugging purposes,
* when tokens need to be presented in a human-readable form. It should not
* be used in production code, as it's not performance-optimized.
*
* @return string
*/
public function __toString(): string {
return $this->get_value() . '<' . $this->id . ',' . $this->get_name() . '>';
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
<?php
class WP_MySQL_Lexer extends WP_MySQL_Native_Lexer {}
@@ -0,0 +1,179 @@
<?php
/**
* Parser node backed by a native (Rust) AST.
*
* Constructed by the native MySQL parser extension. Read methods delegate
* into the Rust-owned AST so children are never copied into PHP unless a
* caller actually walks the tree. On the first mutation (append_child or
* merge_fragment), the node materializes its children into the inherited
* `$children` array and behaves like a plain WP_Parser_Node from then on.
*/
class WP_MySQL_Native_Parser_Node extends WP_Parser_Node {
private $was_mutated = false;
public function __construct( $rule_id, $rule_name ) {
parent::__construct( $rule_id, $rule_name );
}
public function __destruct() {
if ( function_exists( 'wp_sqlite_mysql_native_ast_release_wrapper' ) ) {
wp_sqlite_mysql_native_ast_release_wrapper( $this );
}
}
/** @inheritDoc */
public function append_child( $node ) {
$this->materialize_native_children();
parent::append_child( $node );
}
/** @inheritDoc */
public function merge_fragment( $node ) {
$this->materialize_native_children();
if ( $node instanceof self ) {
$node->materialize_native_children();
}
parent::merge_fragment( $node );
}
/** @inheritDoc */
public function has_child(): bool {
if ( $this->was_mutated ) {
return parent::has_child();
}
return wp_sqlite_mysql_native_ast_has_child( $this );
}
/** @inheritDoc */
public function has_child_node( ?string $rule_name = null ): bool {
if ( $this->was_mutated ) {
return parent::has_child_node( $rule_name );
}
return wp_sqlite_mysql_native_ast_has_child_node( $this, $rule_name );
}
/** @inheritDoc */
public function has_child_token( ?int $token_id = null ): bool {
if ( $this->was_mutated ) {
return parent::has_child_token( $token_id );
}
return wp_sqlite_mysql_native_ast_has_child_token( $this, $token_id );
}
/** @inheritDoc */
public function get_first_child() {
if ( $this->was_mutated ) {
return parent::get_first_child();
}
return wp_sqlite_mysql_native_ast_get_first_child( $this );
}
/** @inheritDoc */
public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
if ( $this->was_mutated ) {
return parent::get_first_child_node( $rule_name );
}
return wp_sqlite_mysql_native_ast_get_first_child_node( $this, $rule_name );
}
/** @inheritDoc */
public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
if ( $this->was_mutated ) {
return parent::get_first_child_token( $token_id );
}
return wp_sqlite_mysql_native_ast_get_first_child_token( $this, $token_id );
}
/** @inheritDoc */
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
if ( $this->was_mutated ) {
return parent::get_first_descendant_node( $rule_name );
}
return wp_sqlite_mysql_native_ast_get_first_descendant_node( $this, $rule_name );
}
/** @inheritDoc */
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
if ( $this->was_mutated ) {
return parent::get_first_descendant_token( $token_id );
}
return wp_sqlite_mysql_native_ast_get_first_descendant_token( $this, $token_id );
}
/** @inheritDoc */
public function get_children(): array {
if ( $this->was_mutated ) {
return parent::get_children();
}
return wp_sqlite_mysql_native_ast_get_children( $this );
}
/** @inheritDoc */
public function get_child_nodes( ?string $rule_name = null ): array {
if ( $this->was_mutated ) {
return parent::get_child_nodes( $rule_name );
}
return wp_sqlite_mysql_native_ast_get_child_nodes( $this, $rule_name );
}
/** @inheritDoc */
public function get_child_tokens( ?int $token_id = null ): array {
if ( $this->was_mutated ) {
return parent::get_child_tokens( $token_id );
}
return wp_sqlite_mysql_native_ast_get_child_tokens( $this, $token_id );
}
/** @inheritDoc */
public function get_descendants(): array {
if ( $this->was_mutated ) {
return parent::get_descendants();
}
return wp_sqlite_mysql_native_ast_get_descendants( $this );
}
/** @inheritDoc */
public function get_descendant_nodes( ?string $rule_name = null ): array {
if ( $this->was_mutated ) {
return parent::get_descendant_nodes( $rule_name );
}
return wp_sqlite_mysql_native_ast_get_descendant_nodes( $this, $rule_name );
}
/** @inheritDoc */
public function get_descendant_tokens( ?int $token_id = null ): array {
if ( $this->was_mutated ) {
return parent::get_descendant_tokens( $token_id );
}
return wp_sqlite_mysql_native_ast_get_descendant_tokens( $this, $token_id );
}
/** @inheritDoc */
public function get_start(): int {
if ( $this->was_mutated ) {
return parent::get_start();
}
return wp_sqlite_mysql_native_ast_get_start( $this );
}
/** @inheritDoc */
public function get_length(): int {
if ( $this->was_mutated ) {
return parent::get_length();
}
return wp_sqlite_mysql_native_ast_get_length( $this );
}
private function materialize_native_children(): void {
if ( $this->was_mutated ) {
return;
}
$this->children = wp_sqlite_mysql_native_ast_get_children( $this );
$this->was_mutated = true;
if ( function_exists( 'wp_sqlite_mysql_native_ast_materialize_wrapper' ) ) {
wp_sqlite_mysql_native_ast_materialize_wrapper( $this );
}
}
}
@@ -0,0 +1,14 @@
<?php
/**
* Native-mode public parser entry point.
*
* Always extends the pure-PHP `WP_Parser` so `$parser instanceof WP_Parser`
* keeps working for callers regardless of whether the Rust extension is
* loaded. The actual parsing work is delegated to a composed
* `WP_MySQL_Native_Parser` instance via the `WP_MySQL_Native_Parser_Impl`
* trait — see that file for the per-method delegation.
*/
class WP_MySQL_Parser extends WP_Parser {
use WP_MySQL_Native_Parser_Impl;
}
@@ -0,0 +1,22 @@
<?php
/**
* Bridge helpers for the optional Rust MySQL lexer/parser extension.
* PHP keeps the grammar object, while Rust owns the exported parser state.
*/
/**
* Export grammar internals for the native parser.
*
* @param WP_Parser_Grammar $grammar Parser grammar.
* @return array<string, mixed>
*/
function wp_sqlite_mysql_native_export_grammar( WP_Parser_Grammar $grammar ): array {
return array(
'highest_terminal_id' => $grammar->highest_terminal_id,
'rules' => $grammar->rules,
'lookahead_is_match_possible' => $grammar->lookahead_is_match_possible,
'rule_names' => $grammar->rule_names,
'fragment_ids' => $grammar->fragment_ids,
);
}
@@ -0,0 +1,55 @@
<?php
/**
* Native-mode `WP_MySQL_Parser` implementation, delivered as a trait.
*
* The class that uses this trait (`WP_MySQL_Parser` in native mode)
* extends the pure-PHP `WP_Parser` so callers' `instanceof WP_Parser`
* checks keep working, while the actual parsing work is delegated to
* the Rust-registered `WP_MySQL_Native_Parser` instance held in
* `$this->native`. `WP_Parser`'s state (`$grammar`, `$tokens`,
* `$position`) stays inert in native mode — the trait's overrides
* never read it.
*
* Adding a public method here is enough to plumb a new public method
* through to the native parser; the using class does not need touching.
*/
trait WP_MySQL_Native_Parser_Impl {
/**
* @var WP_MySQL_Native_Parser
*/
private $native;
/**
* @param WP_Parser_Grammar $grammar
* @param array<WP_Parser_Token>|WP_MySQL_Native_Token_Stream $tokens
*/
public function __construct( WP_Parser_Grammar $grammar, $tokens ) {
// WP_Parser's `array $tokens` constructor signature can't accept
// the native token stream object; its `$this->tokens` /
// `$this->position` state is inert in native mode anyway, so we
// pass an empty array to satisfy the parent contract and keep
// the actual tokens on the native parser.
parent::__construct( $grammar, array() );
$this->native = new WP_MySQL_Native_Parser( $grammar, $tokens );
}
/**
* @param array<WP_Parser_Token>|WP_MySQL_Native_Token_Stream $tokens
*/
public function reset_tokens( $tokens ): void {
$this->native->reset_tokens( $tokens );
}
public function next_query(): bool {
return $this->native->next_query();
}
public function get_query_ast(): ?WP_Parser_Node {
return $this->native->get_query_ast();
}
public function parse() {
return $this->native->parse();
}
}
@@ -0,0 +1,140 @@
<?php
/**
* A parser grammar.
*
* This class represents a parser grammar that can be consumed by WP_Parser.
* It loads a compressed grammar from a PHP array, inflates it to an internal
* representation, and precomputes a lookup table for quick branch selection.
*
* @TODO: Add more details about the grammar implementation.
*/
class WP_Parser_Grammar {
/**
* ID for a special grammar rule that represents an empty "ε" (epsilon) rule.
*
* An "ε" rule in a grammar is a rule that matches an empty input of 0 bytes.
* It can be used to represent optional grammar productions, and it is helpful
* for expanding 0-or-1, 0-or-more, and 1-or-more quantifiers into simple rules.
*
* @TODO Investigate whether we can prevent possible conflict with a token ID.
* The MySQL grammar doesn't define a token with ID "0", but generally
* token IDs are not guaranteed to always satisfy this condition.
*/
const EMPTY_RULE_ID = 0;
/**
* @TODO: Review and document these properties and their visibility.
*/
public $rules;
public $rule_names;
public $fragment_ids;
public $lookahead_is_match_possible = array();
public $lowest_non_terminal_id;
public $highest_terminal_id;
public $native_grammar;
public function __construct( array $rules ) {
$this->inflate( $rules );
}
public function get_rule_name( $rule_id ) {
return $this->rule_names[ $rule_id ];
}
public function get_rule_id( $rule_name ) {
return array_search( $rule_name, $this->rule_names, true );
}
/**
* Inflate the grammar to an internal representation optimized for parsing.
*
* The input grammar is a compressed PHP array to minimize the file size.
* Every rule and token in the compressed grammar is encoded as an integer.
*/
private function inflate( $grammar ) {
$this->lowest_non_terminal_id = $grammar['rules_offset'];
$this->highest_terminal_id = $this->lowest_non_terminal_id - 1;
foreach ( $grammar['rules_names'] as $rule_index => $rule_name ) {
$this->rule_names[ $rule_index + $grammar['rules_offset'] ] = $rule_name;
$this->rules[ $rule_index + $grammar['rules_offset'] ] = array();
/**
* Treat all intermediate rules as fragments to inline before returning
* the final parse tree to the API consumer.
*
* The original grammar was too difficult to parse with rules like:
*
* query ::= EOF | ((simpleStatement | beginWork) ((SEMICOLON_SYMBOL EOF?) | EOF))
*
* We've factored rule fragments, such as `EOF?`, into separate rules, such as `%EOF_zero_or_one`.
* This is super useful for parsing, but it limits the API consumer's ability to
* reason about the parse tree.
*
* Fragments are intermediate rules that are not part of the original grammar.
* They are prefixed with a "%" to be distinguished from the original rules.
*/
if ( '%' === $rule_name[0] ) {
$this->fragment_ids[ $rule_index + $grammar['rules_offset'] ] = true;
}
}
$this->rules = array();
foreach ( $grammar['grammar'] as $rule_index => $branches ) {
$rule_id = $rule_index + $grammar['rules_offset'];
$this->rules[ $rule_id ] = $branches;
}
/**
* Compute a rule => [token => true] lookup table for each rule
* that starts with a terminal OR with another rule that already
* has a lookahead mapping.
*
* This is similar to left-factoring the grammar, even if not quite
* the same.
*
* This enables us to quickly bail out from checking branches that
* cannot possibly match the current token. This increased the parser
* speed by a whopping 80%!
*
* @TODO: Explore these possible next steps:
*
* * Compute a rule => [token => branch[]] list lookup table and only
* process the branches that have a chance of matching the current token.
* * Actually left-factor the grammar as much as possible. This, however,
* could inflate the serialized grammar size.
*/
// 5 iterations seem to give us all the speed gains we can get from this.
for ( $i = 0; $i < 5; $i++ ) {
foreach ( $grammar['grammar'] as $rule_index => $branches ) {
$rule_id = $rule_index + $grammar['rules_offset'];
if ( isset( $this->lookahead_is_match_possible[ $rule_id ] ) ) {
continue;
}
$rule_lookup = array();
$first_symbol_can_be_expanded_to_all_terminals = true;
foreach ( $branches as $branch ) {
$terminals = false;
$branch_starts_with_terminal = $branch[0] < $this->lowest_non_terminal_id;
if ( $branch_starts_with_terminal ) {
$terminals = array( $branch[0] );
} elseif ( isset( $this->lookahead_is_match_possible[ $branch[0] ] ) ) {
$terminals = array_keys( $this->lookahead_is_match_possible[ $branch[0] ] );
}
if ( false === $terminals ) {
$first_symbol_can_be_expanded_to_all_terminals = false;
break;
}
foreach ( $terminals as $terminal ) {
$rule_lookup[ $terminal ] = true;
}
}
if ( $first_symbol_can_be_expanded_to_all_terminals ) {
$this->lookahead_is_match_possible[ $rule_id ] = $rule_lookup;
}
}
}
}
}
@@ -0,0 +1,384 @@
<?php
/**
* A node in parse tree.
*
* This class represents a node in the parse tree that is produced by WP_Parser.
* A node corresponds to the related grammar rule that was matched by the parser.
* Each node can contain children, consisting of other nodes and grammar tokens.
* In this way, a parser node constitutes a recursive structure that represents
* a parse (sub)tree at each level of the full grammar tree.
*/
class WP_Parser_Node {
/**
* @TODO: Review and document these properties and their visibility.
*/
public $rule_id;
public $rule_name;
protected $children = array();
public function __construct( $rule_id, $rule_name ) {
$this->rule_id = $rule_id;
$this->rule_name = $rule_name;
}
public function append_child( $node ) {
$this->children[] = $node;
}
/**
* Flatten the matched rule fragments as if their children were direct
* descendants of the current rule.
*
* What are rule fragments?
*
* When we initially parse the grammar file, it has compound rules such
* as this one:
*
* query ::= EOF | ((simpleStatement | beginWork) ((SEMICOLON_SYMBOL EOF?) | EOF))
*
* Building a parser that can understand such rules is way more complex than building
* a parser that only follows simple rules, so we flatten those compound rules into
* simpler ones. The above rule would be flattened to:
*
* query ::= EOF | %query0
* %query0 ::= %%query01 %%query02
* %%query01 ::= simpleStatement | beginWork
* %%query02 ::= SEMICOLON_SYMBOL EOF_zero_or_one | EOF
* EOF_zero_or_one ::= EOF | ε
*
* This factorization happens in "convert-grammar.php".
*
* "Fragments" are intermediate artifacts whose names are not in the original grammar.
* They are extremely useful for the parser, but the API consumer should never have to
* worry about them. Fragment names start with a percent sign ("%").
*
* The code below inlines every fragment back in its parent rule.
*
* We could optimize this. The current $match may be discarded later on so any inlining
* effort here would be wasted. However, inlining seems cheap and doing it bottom-up here
* is **much** easier than reprocessing the parse tree top-down later on.
*
* The following parse tree:
*
* [
* 'query' => [
* [
* '%query01' => [
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ],
* '%query02' => [
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ]
* ],
* ]
* ]
* ]
* ]
* ]
*
* Would be inlined as:
*
* [
* 'query' => [
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ]
* ],
* [
* 'simpleStatement' => [
* MySQLToken(MySQLLexer::WITH_SYMBOL, 'WITH')
* ]
* ]
* ]
* ]
*/
public function merge_fragment( $node ) {
$this->children = array_merge( $this->children, $node->children );
}
/**
* Check if this node has any child nodes or tokens.
*
* @return bool True if this node has any child nodes or tokens, false otherwise.
*/
public function has_child(): bool {
return count( $this->children ) > 0;
}
/**
* Check if this node has any child nodes.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return bool True if any child nodes are found, false otherwise.
*/
public function has_child_node( ?string $rule_name = null ): bool {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Node
&& ( null === $rule_name || $child->rule_name === $rule_name )
) {
return true;
}
}
return false;
}
/**
* Check if this node has any child tokens.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return bool True if any child tokens are found, false otherwise.
*/
public function has_child_token( ?int $token_id = null ): bool {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Token
&& ( null === $token_id || $child->id === $token_id )
) {
return true;
}
}
return false;
}
/**
* Get the first child node or token of this node.
*
* @return WP_Parser_Node|WP_Parser_Token|null The first child node or token;
* null when no children are found.
*/
public function get_first_child() {
return $this->children[0] ?? null;
}
/**
* Get the first child node of this node.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node|null The first matching child node; null when no children are found.
*/
public function get_first_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Node
&& ( null === $rule_name || $child->rule_name === $rule_name )
) {
return $child;
}
}
return null;
}
/**
* Get the first child token of this node.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token|null The first matching child token; null when no children are found.
*/
public function get_first_child_token( ?int $token_id = null ): ?WP_Parser_Token {
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Token
&& ( null === $token_id || $child->id === $token_id )
) {
return $child;
}
}
return null;
}
/**
* Get the first descendant node of this node.
*
* The node children are traversed recursively in a depth-first order until
* a matching descendant node is found, or the entire subtree is searched.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node|null The first matching descendant node; null when no descendants are found.
*/
public function get_first_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
for ( $i = 0; $i < count( $this->children ); $i++ ) {
$child = $this->children[ $i ];
if ( ! $child instanceof WP_Parser_Node ) {
continue;
}
if ( null === $rule_name || $child->rule_name === $rule_name ) {
return $child;
}
$node = $child->get_first_descendant_node( $rule_name );
if ( $node ) {
return $node;
}
}
return null;
}
/**
* Get the first descendant token of this node.
*
* The node children are traversed recursively in a depth-first order until
* a matching descendant token is found, or the entire subtree is searched.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token|null The first matching descendant token; null when no descendants are found.
*/
public function get_first_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
for ( $i = 0; $i < count( $this->children ); $i++ ) {
$child = $this->children[ $i ];
if ( $child instanceof WP_Parser_Token ) {
if ( null === $token_id || $child->id === $token_id ) {
return $child;
}
} else {
$token = $child->get_first_descendant_token( $token_id );
if ( $token ) {
return $token;
}
}
}
return null;
}
/**
* Get all children of this node.
*
* @return array<WP_Parser_Node|WP_Parser_Token> An array of all child nodes and tokens of this node.
*/
public function get_children(): array {
return $this->children;
}
/**
* Get all child nodes of this node.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node[] An array of all matching child nodes.
*/
public function get_child_nodes( ?string $rule_name = null ): array {
$nodes = array();
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Node
&& ( null === $rule_name || $child->rule_name === $rule_name )
) {
$nodes[] = $child;
}
}
return $nodes;
}
/**
* Get all child tokens of this node.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token[] An array of all matching child tokens.
*/
public function get_child_tokens( ?int $token_id = null ): array {
$tokens = array();
foreach ( $this->children as $child ) {
if (
$child instanceof WP_Parser_Token
&& ( null === $token_id || $child->id === $token_id )
) {
$tokens[] = $child;
}
}
return $tokens;
}
/**
* Get all descendants of this node.
*
* The descendants are collected using a depth-first pre-order NLR traversal.
* This produces a natural ordering that corresponds to the original input.
*
* @return array<WP_Parser_Node|WP_Parser_Token> An array of all descendant nodes and tokens of this node.
*/
public function get_descendants(): array {
$descendants = array();
foreach ( $this->children as $child ) {
if ( $child instanceof WP_Parser_Node ) {
$descendants[] = $child;
$descendants = array_merge( $descendants, $child->get_descendants() );
} else {
$descendants[] = $child;
}
}
return $descendants;
}
/**
* Get all descendant nodes of this node.
*
* The descendants are collected using a depth-first pre-order NLR traversal.
* This produces a natural ordering that corresponds to the original input.
* All matching nodes are collected during the traversal.
*
* @param string|null $rule_name Optional. A node rule name to check for.
* @return WP_Parser_Node[] An array of all matching descendant nodes.
*/
public function get_descendant_nodes( ?string $rule_name = null ): array {
$nodes = array();
foreach ( $this->children as $child ) {
if ( ! $child instanceof WP_Parser_Node ) {
continue;
}
if ( null === $rule_name || $child->rule_name === $rule_name ) {
$nodes[] = $child;
}
$nodes = array_merge( $nodes, $child->get_descendant_nodes( $rule_name ) );
}
return $nodes;
}
/**
* Get all descendant tokens of this node.
*
* The descendants are collected using a depth-first pre-order NLR traversal.
* This produces a natural ordering that corresponds to the original input.
* All matching tokens are collected during the traversal.
*
* @param int|null $token_id Optional. A token ID to check for.
* @return WP_Parser_Token[] An array of all matching descendant tokens.
*/
public function get_descendant_tokens( ?int $token_id = null ): array {
$tokens = array();
foreach ( $this->children as $child ) {
if ( $child instanceof WP_Parser_Token ) {
if ( null === $token_id || $child->id === $token_id ) {
$tokens[] = $child;
}
} else {
$tokens = array_merge( $tokens, $child->get_descendant_tokens( $token_id ) );
}
}
return $tokens;
}
/**
* Get the byte offset in the input string where this node begins.
*
* @return int The byte offset in the input string where this node begins.
*/
public function get_start(): int {
return $this->get_first_descendant_token()->start;
}
/**
* Get the byte length of this node in the input string.
*
* @return int The byte length of this node in the input string.
*/
public function get_length(): int {
$tokens = $this->get_descendant_tokens();
$first_token = $tokens[0];
$last_token = $tokens[ count( $tokens ) - 1 ];
return $last_token->start + $last_token->length - $first_token->start;
}
}
@@ -0,0 +1,77 @@
<?php
/**
* A token, representing a leaf in the parse tree.
*
* This class represents a token that is consumed and recognized by WP_Parser.
* In a parse tree, a token represent a leaf, that is, a node without children.
* It is a simple generic container for a token ID and value, that can be used
* as a base class and extended for specific use cases.
*/
class WP_Parser_Token {
/**
* Token ID represented as an integer constant.
*
* @var int $id
*/
public $id;
/**
* Byte offset in the input where the token begins.
*
* @var int
*/
public $start;
/**
* Byte length of the token in the input.
*
* @var int
*/
public $length;
/**
* Input bytes from which the token was parsed.
*
* @var string
*/
protected $input;
/**
* Constructor.
*
* @param int $id Token type.
* @param int $start Byte offset in the input where the token begins.
* @param int $length Byte length of the token in the input.
* @param string $input Input bytes from which the token was parsed.
*/
public function __construct(
int $id,
int $start,
int $length,
string $input
) {
$this->id = $id;
$this->start = $start;
$this->length = $length;
$this->input = $input;
}
/**
* Get the raw bytes of the token from the input.
*
* @return string The token bytes.
*/
public function get_bytes(): string {
return substr( $this->input, $this->start, $this->length );
}
/**
* Get the real unquoted value of the token.
*
* @return string The token value.
*/
public function get_value(): string {
return $this->get_bytes();
}
}
@@ -0,0 +1,124 @@
<?php
/**
* A recursive descent parser.
*
* This is a dynamic recursive descent parser that can parse LL grammars.
*
* @TODO: Add a detailed description and list the properties that a grammar must
* satisfy in order to be supported by this parser (e.g., no left recursion).
*/
class WP_Parser {
protected $grammar;
protected $tokens;
protected $position;
public function __construct( WP_Parser_Grammar $grammar, array $tokens ) {
$this->grammar = $grammar;
$this->tokens = $tokens;
$this->position = 0;
}
public function parse() {
// @TODO: Make the starting rule lookup non-grammar-specific.
$query_rule_id = $this->grammar->get_rule_id( 'query' );
$ast = $this->parse_recursive( $query_rule_id );
return false === $ast ? null : $ast;
}
private function parse_recursive( $rule_id ) {
$is_terminal = $rule_id <= $this->grammar->highest_terminal_id;
if ( $is_terminal ) {
if ( $this->position >= count( $this->tokens ) ) {
return false;
}
if ( WP_Parser_Grammar::EMPTY_RULE_ID === $rule_id ) {
return true;
}
if ( $this->tokens[ $this->position ]->id === $rule_id ) {
++$this->position;
return $this->tokens[ $this->position - 1 ];
}
return false;
}
$branches = $this->grammar->rules[ $rule_id ];
if ( ! count( $branches ) ) {
return false;
}
// Bale out from processing the current branch if none of its rules can
// possibly match the current token.
if ( isset( $this->grammar->lookahead_is_match_possible[ $rule_id ] ) ) {
$token_id = $this->tokens[ $this->position ]->id;
if (
! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ $token_id ] ) &&
! isset( $this->grammar->lookahead_is_match_possible[ $rule_id ][ WP_Parser_Grammar::EMPTY_RULE_ID ] )
) {
return false;
}
}
$rule_name = $this->grammar->rule_names[ $rule_id ];
$starting_position = $this->position;
foreach ( $branches as $branch ) {
$this->position = $starting_position;
$node = new WP_Parser_Node( $rule_id, $rule_name );
$branch_matches = true;
foreach ( $branch as $subrule_id ) {
$subnode = $this->parse_recursive( $subrule_id );
if ( false === $subnode ) {
$branch_matches = false;
break;
} elseif ( true === $subnode ) {
/*
* The subrule was matched without actually matching a token.
* This means a special empty "ε" (epsilon) rule was matched.
* An "ε" rule in a grammar matches an empty input of 0 bytes.
* It is used to represent optional grammar productions.
*/
continue;
} elseif ( is_array( $subnode ) && 0 === count( $subnode ) ) {
continue;
}
if ( is_array( $subnode ) && ! count( $subnode ) ) {
continue;
}
if ( isset( $this->grammar->fragment_ids[ $subrule_id ] ) ) {
$node->merge_fragment( $subnode );
} else {
$node->append_child( $subnode );
}
}
// Negative lookahead for INTO after a valid SELECT statement.
// If we match a SELECT statement, but there is an INTO keyword after it,
// we're in the wrong branch and need to leave matching to a later rule.
// @TODO: Extract this to the "WP_MySQL_Parser" class, or add support
// for right-associative rules, which could solve this.
// See: https://github.com/mysql/mysql-workbench/blob/8.0.38/library/parsers/grammars/MySQLParser.g4#L994
// See: https://github.com/antlr/antlr4/issues/488
$la = $this->tokens[ $this->position ] ?? null;
if ( $la && 'selectStatement' === $rule_name && WP_MySQL_Lexer::INTO_SYMBOL === $la->id ) {
$branch_matches = false;
}
if ( true === $branch_matches ) {
break;
}
}
if ( ! $branch_matches ) {
$this->position = $starting_position;
return false;
}
if ( ! $node->has_child() ) {
return true;
}
return $node;
}
}
@@ -0,0 +1,68 @@
<?php
/**
* Polyfills for PHP 8.0 string functions.
*
* Implementation follows the Symfony polyfill-php80 package.
*
* @see https://github.com/symfony/polyfill-php80
*
* @package wp-sqlite-integration
*/
if ( ! function_exists( 'str_starts_with' ) ) {
/**
* Check if a string starts with a specific substring.
*
* @param string $haystack The string to search in.
* @param string $needle The string to search for.
*
* @see https://www.php.net/manual/en/function.str-starts-with
*
* @return bool
*/
function str_starts_with( string $haystack, string $needle ) {
return 0 === strncmp( $haystack, $needle, strlen( $needle ) );
}
}
if ( ! function_exists( 'str_contains' ) ) {
/**
* Check if a string contains a specific substring.
*
* @param string $haystack The string to search in.
* @param string $needle The string to search for.
*
* @see https://www.php.net/manual/en/function.str-contains
*
* @return bool
*/
function str_contains( string $haystack, string $needle ) {
return '' === $needle || false !== strpos( $haystack, $needle );
}
}
if ( ! function_exists( 'str_ends_with' ) ) {
/**
* Check if a string ends with a specific substring.
*
* @param string $haystack The string to search in.
* @param string $needle The string to search for.
*
* @see https://www.php.net/manual/en/function.str-ends-with
*
* @return bool
*/
function str_ends_with( string $haystack, string $needle ) {
if ( '' === $needle || $needle === $haystack ) {
return true;
}
if ( '' === $haystack ) {
return false;
}
$needle_length = strlen( $needle );
return $needle_length <= strlen( $haystack ) && 0 === substr_compare( $haystack, $needle, -$needle_length );
}
}
@@ -0,0 +1,382 @@
<?php
/*
* The SQLite driver uses PDO. Enable PDO function calls:
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDOStatement
*
* PDO uses camel case naming, enable non-snake case:
* phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
* phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
*
* PDO uses $class as a variable name, enable it:
* phpcs:disable Universal.NamingConventions.NoReservedKeywordParameterNames.classFound
*
* Some PDOStatement methods use $var as a variable name, enable it:
* phpcs:disable Universal.NamingConventions.NoReservedKeywordParameterNames.varFound
*
* We use traits to support different PHP versions with incompatible PDO statement
* method signatures. For that, enable multiple object structures in one file:
* phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
*/
/**
* Some PDOStatement methods are not compatible across different PHP versions.
* To address "Declaration of ... should be compatible with ..." PHP warnings,
* we conditionally define traits with different APIs based on the PHP version.
*/
if ( PHP_VERSION_ID < 80000 ) {
trait WP_PDO_Proxy_Statement_PHP_Compat {
/**
* Set the default fetch mode for this statement.
*
* @param int $mode The fetch mode to set as the default.
* @param mixed $params Additional parameters for the default fetch mode.
* @return bool True on success, false on failure.
*/
public function setFetchMode( $mode, $params = null ): bool {
// Do not pass additional arguments when they are NULL to prevent
// "fetch mode doesn't allow any extra arguments" error.
if ( null === $params ) {
return $this->setDefaultFetchMode( $mode );
}
return $this->setDefaultFetchMode( $mode, $params );
}
/**
* Fetch all remaining rows from the result set.
*
* @param int $mode The fetch mode to use.
* @param mixed $class_name With PDO::FETCH_CLASS, the name of the class to instantiate.
* @param mixed $constructor_args With PDO::FETCH_CLASS, the parameters to pass to the class constructor.
* @return array The result set as an array of rows.
*/
public function fetchAll( $mode = null, $class_name = null, $constructor_args = null ): array {
// Do not pass additional arguments when they are NULL to prevent
// "Extraneous additional parameters" error.
if ( null === $class_name && null === $constructor_args ) {
return $this->fetchAllRows( $mode );
}
return $this->fetchAllRows( $mode, $class_name, $constructor_args );
}
}
} else {
trait WP_PDO_Proxy_Statement_PHP_Compat {
/**
* Set the default fetch mode for this statement.
*
* @param int $mode The fetch mode to set as the default.
* @param mixed $args Additional parameters for the default fetch mode.
* @return bool True on success, false on failure.
*/
#[ReturnTypeWillChange]
public function setFetchMode( $mode, ...$args ): bool {
return $this->setDefaultFetchMode( $mode, ...$args );
}
/**
* Fetch all remaining rows from the result set.
*
* @param int $mode The fetch mode to use.
* @param mixed $args Additional parameters for the fetch mode.
* @return array The result set as an array of rows.
*/
public function fetchAll( $mode = PDO::FETCH_DEFAULT, ...$args ): array {
return $this->fetchAllRows( $mode, ...$args );
}
}
}
/**
* PDOStatement implementation that operates on in-memory data.
*
* This class implements a complete PDOStatement interface on top of PHP arrays.
* It is used for result sets that are composed or transformed in the PHP layer.
*
* PDO supports the following fetch modes:
* - PDO::FETCH_DEFAULT: current default fetch mode (available from PHP 8.0)
* - PDO::FETCH_BOTH: default
* - PDO::FETCH_NUM: numeric array
* - PDO::FETCH_ASSOC: associative array
* - PDO::FETCH_NAMED: associative array retaining duplicate columns
* - PDO::FETCH_COLUMN: single column value [1 extra arg]
* - PDO::FETCH_KEY_PAIR: key-value pair
* - PDO::FETCH_OBJ: object (stdClass)
* - PDO::FETCH_CLASS: object (custom class) [1-2 extra args]
* - PDO::FETCH_INTO: update an exisisting object, can't be used with fetchAll() [1 extra arg]
* - PDO::FETCH_LAZY: lazy fetch via PDORow, can't be used with fetchAll()
* - PDO::FETCH_BOUND: bind values to PHP variables, can't be used with fetchAll()
* - PDO::FETCH_FUNC: custom function, only works with fetchAll(), can't be default [1 extra arg]
*/
class WP_PDO_Proxy_Statement extends PDOStatement {
use WP_PDO_Proxy_Statement_PHP_Compat;
/**
* The original PDO statement.
*
* @var PDOStatement
*/
private $statement;
/**
* The number of affected rows.
*
* @var int|null
*/
private $affected_rows;
/**
* Constructor.
*
* @param PDOStatement $statement The original PDO statement.
* @param int $affected_rows The number of affected rows.
*/
public function __construct(
PDOStatement $statement,
?int $affected_rows = null
) {
$this->statement = $statement;
$this->affected_rows = $affected_rows;
}
/**
* Execute a prepared statement.
*
* @param mixed $params The values to bind to the parameters of the prepared statement.
* @return bool True on success, false on failure.
*/
public function execute( $params = null ): bool {
return $this->statement->execute( $params );
}
/**
* Get the number of columns in the result set.
*
* @return int The number of columns in the result set.
*/
public function columnCount(): int {
return $this->statement->columnCount();
}
/**
* Get the number of rows affected by the statement.
*
* @return int The number of rows affected by the statement.
*/
public function rowCount(): int {
return $this->affected_rows ?? $this->statement->rowCount();
}
/**
* Fetch the next row from the result set.
*
* @param int|null $mode The fetch mode. Controls how the row is returned.
* Default: PDO::FETCH_DEFAULT (null for PHP < 8.0)
* @param int|null $cursorOrientation The cursor orientation. Controls which row is returned.
* Default: PDO::FETCH_ORI_NEXT (null for PHP < 8.0)
* @param int|null $cursorOffset The cursor offset. Controls which row is returned.
* Default: 0 (null for PHP < 8.0)
* @return mixed The row data formatted according to the fetch mode;
* false if there are no more rows or a failure occurs.
*/
#[ReturnTypeWillChange]
public function fetch(
$mode = 0, // PDO::FETCH_DEFAULT (available from PHP 8.0)
$cursorOrientation = 0,
$cursorOffset = 0
) {
return $this->statement->fetch( $mode, $cursorOrientation, $cursorOffset );
}
/**
* Fetch a single column from the next row of a result set.
*
* @param int $column The index of the column to fetch (0-indexed).
* @return mixed The value of the column; false if there are no more rows.
*/
#[ReturnTypeWillChange]
public function fetchColumn( $column = 0 ) {
return $this->statement->fetchColumn( $column );
}
/**
* Fetch the next row as an object.
*
* @param string $class The name of the class to instantiate.
* @param array $constructorArgs The parameters to pass to the class constructor.
* @return object The next row as an object.
*/
#[ReturnTypeWillChange]
public function fetchObject( $class = 'stdClass', $constructorArgs = array() ) {
return $this->statement->fetchObject( $class, $constructorArgs );
}
/**
* Get metadata for a column in a result set.
*
* @param int $column The index of the column (0-indexed).
* @return array|false The column metadata as an associative array,
* or false if the column does not exist.
*/
public function getColumnMeta( $column ): array {
throw new RuntimeException( 'Not implemented' );
}
/**
* Fetch the SQLSTATE associated with the last statement operation.
*
* @return string|null The SQLSTATE error code (as defined by the ANSI SQL standard),
* or null if there is no error.
*/
public function errorCode(): ?string {
throw new RuntimeException( 'Not implemented' );
}
/**
* Fetch error information associated with the last statement operation.
*
* @return array The array consists of at least the following fields:
* 0: SQLSTATE error code (as defined by the ANSI SQL standard).
* 1: Driver-specific error code.
* 2: Driver-specific error message.
*/
public function errorInfo(): array {
throw new RuntimeException( 'Not implemented' );
}
/**
* Get a statement attribute.
*
* @param int $attribute The attribute to get.
* @return mixed The value of the attribute.
*/
#[ReturnTypeWillChange]
public function getAttribute( $attribute ) {
return $this->statement->getAttribute( $attribute );
}
/**
* Set a statement attribute.
*
* @param int $attribute The attribute to set.
* @param mixed $value The value of the attribute.
* @return bool True on success, false on failure.
*/
public function setAttribute( $attribute, $value ): bool {
return $this->statement->setAttribute( $attribute, $value );
}
/**
* Get result set as iterator.
*
* @return Iterator The iterator for the result set.
*/
public function getIterator(): Iterator {
throw new RuntimeException( 'Not implemented' );
}
/**
* Advances to the next rowset in a multi-rowset statement handle.
*
* @return bool True on success, false on failure.
*/
public function nextRowset(): bool {
throw new RuntimeException( 'Not implemented' );
}
/**
* Closes the cursor, enabling the statement to be executed again.
*
* @return bool True on success, false on failure.
*/
public function closeCursor(): bool {
throw new RuntimeException( 'Not implemented' );
}
/**
* Bind a column to a PHP variable.
*
* @param int|string $column Number of the column (1-indexed) or name of the column in the result set.
* @param mixed $var PHP variable to which the column will be bound.
* @param int $type Data type of the parameter, specified by the PDO::PARAM_* constants.
* @param int $maxLength A hint for pre-allocation.
* @param mixed $driverOptions Optional parameters for the driver.
* @return bool True on success, false on failure.
*/
public function bindColumn( $column, &$var, $type = null, $maxLength = null, $driverOptions = null ): bool {
throw new RuntimeException( 'Not implemented' );
}
/**
* Bind a parameter to a PHP variable.
*
* @param int|string $param Parameter identifier. Either a 1-indexed position of the parameter or a named parameter.
* @param mixed $var PHP variable to which the parameter will be bound.
* @param int $type Data type of the parameter, specified by the PDO::PARAM_* constants.
* @param int $maxLength Length of the data type.
* @param mixed $driverOptions Optional parameters for the driver.
* @return bool True on success, false on failure.
*/
public function bindParam( $param, &$var, $type = PDO::PARAM_STR, $maxLength = 0, $driverOptions = null ): bool {
throw new RuntimeException( 'Not implemented' );
}
/**
* Bind a value to a parameter.
*
* @param int|string $param Parameter identifier. Either a 1-indexed position of the parameter or a named parameter.
* @param mixed $value The value to bind to the parameter.
* @param int $type Data type of the parameter, specified by the PDO::PARAM_* constants.
* @return bool True on success, false on failure.
*/
public function bindValue( $param, $value, $type = PDO::PARAM_STR ): bool {
throw new RuntimeException( 'Not implemented' );
}
/**
* Dump information about the statement.
*
* Dupms the SQL query and parameters information.
*
* @return bool|null Returns null, or false on failure.
*/
public function debugDumpParams(): ?bool {
throw new RuntimeException( 'Not implemented' );
}
/**
* Fetch all remaining rows from the result set.
*
* This is used internally by the "WP_PDO_Proxy_Statement_PHP_Compat" trait,
* that is defined conditionally based on the current PHP version.
*
* @param int $mode The fetch mode to use.
* @param mixed $args Additional parameters for the fetch mode.
* @return array The result set as an array of rows.
*/
private function fetchAllRows( $mode = null, ...$args ): array {
return $this->statement->fetchAll( $mode, ...$args );
}
/**
* Set the default fetch mode for this statement.
*
* This is used internally by the "WP_PDO_Proxy_Statement_PHP_Compat" trait,
* that is defined conditionally based on the current PHP version.
*
* @param int $mode The fetch mode to set as the default.
* @param mixed $args Additional parameters for the default fetch mode.
* @return bool True on success, false on failure.
*/
private function setDefaultFetchMode( $mode, ...$args ): bool {
return $this->statement->setFetchMode( $mode, ...$args );
}
}
/**
* Polyfill ValueError for PHP < 8.0.
*/
if ( PHP_VERSION_ID < 80000 && ! class_exists( ValueError::class ) ) {
class ValueError extends Error {
}
}
@@ -0,0 +1,273 @@
<?php
/**
* SQLite database configurator.
*
* This class initializes and configures the SQLite database, so that it can be
* used by the SQLite driver to translate and emulate MySQL queries in SQLite.
*
* The configurator ensures that tables required for emulating MySQL behaviors
* are created and populated with necessary data. It is also able to partially
* repair and update these tables and metadata in case of database corruption.
*/
class WP_SQLite_Configurator {
/**
* The SQLite driver instance.
*
* @var WP_PDO_MySQL_On_SQLite
*/
private $driver;
/**
* A service for managing MySQL INFORMATION_SCHEMA tables in SQLite.
*
* @var WP_SQLite_Information_Schema_Builder
*/
private $schema_builder;
/**
* A service for reconstructing the MySQL INFORMATION_SCHEMA tables in SQLite.
*
* @var WP_SQLite_Information_Schema_Reconstructor
*/
private $schema_reconstructor;
/**
* Constructor.
*
* @param WP_PDO_MySQL_On_SQLite $driver The SQLite driver instance.
* @param WP_SQLite_Information_Schema_Builder $schema_builder The information schema builder instance.
*/
public function __construct(
WP_PDO_MySQL_On_SQLite $driver,
WP_SQLite_Information_Schema_Builder $schema_builder
) {
$this->driver = $driver;
$this->schema_builder = $schema_builder;
$this->schema_reconstructor = new WP_SQLite_Information_Schema_Reconstructor(
$driver,
$schema_builder
);
}
/**
* Ensure that the SQLite database is configured.
*
* This method checks if the database is configured for the latest SQLite
* driver version, and if it is not, it will configure the database.
*/
public function ensure_database_configured(): void {
$version = SQLITE_DRIVER_VERSION;
$db_version = $this->driver->get_saved_driver_version();
if ( version_compare( $version, $db_version ) > 0 ) {
$this->configure_database();
}
}
/**
* Configure the SQLite database.
*
* This method creates tables used for emulating MySQL behaviors in SQLite,
* and populates them with necessary data. When it is used with an already
* configured database, it will update the configuration as per the current
* SQLite driver version and attempt to repair any configuration corruption.
*/
public function configure_database(): void {
// Use an EXCLUSIVE transaction to prevent multiple connections
// from attempting to configure the database at the same time.
$this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' );
try {
$this->ensure_global_variables_table();
$this->schema_builder->ensure_information_schema_tables();
$this->schema_reconstructor->ensure_correct_information_schema();
$this->save_current_driver_version();
$this->ensure_database_data();
} catch ( Throwable $e ) {
$this->driver->execute_sqlite_query( 'ROLLBACK' );
throw $e;
}
$this->driver->execute_sqlite_query( 'COMMIT' );
}
/**
* Ensure that the global variables table exists.
*
* This method configures a database table to store MySQL global variables
* and other internal configuration values.
*/
private function ensure_global_variables_table(): void {
$this->driver->execute_sqlite_query(
sprintf(
'CREATE TABLE IF NOT EXISTS %s (name TEXT PRIMARY KEY, value TEXT)',
$this->driver->get_connection()->quote_identifier(
WP_PDO_MySQL_On_SQLite::GLOBAL_VARIABLES_TABLE_NAME
)
)
);
}
/**
* Ensure that the database data is correctly populated.
*
* This method ensures that the "INFORMATION_SCHEMA.SCHEMATA" table contains
* records for both the "INFORMATION_SCHEMA" database and the user database.
* At the moment, only a single user database is supported.
*
* Additionally, this method ensures that the user database name is stored
* correctly in all the information schema tables.
*/
public function ensure_database_data(): void {
// Get all databases from the "SCHEMATA" table.
$schemata_table = $this->schema_builder->get_table_name( false, 'schemata' );
$databases = $this->driver->execute_sqlite_query(
sprintf(
'SELECT SCHEMA_NAME FROM %s',
$this->driver->get_connection()->quote_identifier( $schemata_table )
)
)->fetchAll( PDO::FETCH_COLUMN ); // phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
// Ensure that the "INFORMATION_SCHEMA" database record exists.
if ( ! in_array( 'information_schema', $databases, true ) ) {
$this->driver->execute_sqlite_query(
sprintf(
'INSERT INTO %s (SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME) VALUES (?, ?, ?)',
$this->driver->get_connection()->quote_identifier( $schemata_table )
),
// The "INFORMATION_SCHEMA" database stays on "utf8mb3" even in MySQL 8 and 9.
array( 'information_schema', 'utf8mb3', 'utf8mb3_general_ci' )
);
}
// Get the existing user database name.
$existing_user_db_name = null;
foreach ( $databases as $database ) {
if ( 'information_schema' !== strtolower( $database ) ) {
$existing_user_db_name = $database;
break;
}
}
// Ensure that the user database record exists.
if ( null === $existing_user_db_name ) {
$existing_user_db_name = WP_SQLite_Information_Schema_Builder::SAVED_DATABASE_NAME;
$this->driver->execute_sqlite_query(
sprintf(
'INSERT INTO %s (SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME) VALUES (?, ?, ?)',
$this->driver->get_connection()->quote_identifier( $schemata_table )
),
// @TODO: This should probably be version-dependent.
// Before MySQL 8, the default was different.
array( $existing_user_db_name, 'utf8mb4', 'utf8mb4_0900_ai_ci' )
);
}
// Migrate from older versions without dynamic database names.
$saved_database_name = WP_SQLite_Information_Schema_Builder::SAVED_DATABASE_NAME;
if ( $saved_database_name !== $existing_user_db_name ) {
// INFORMATION_SCHEMA.SCHEMATA
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s SET SCHEMA_NAME = ? WHERE SCHEMA_NAME != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $schemata_table )
),
array( $saved_database_name )
);
// INFORMATION_SCHEMA.TABLES
$tables_table = $this->schema_builder->get_table_name( false, 'tables' );
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s SET TABLE_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $tables_table )
),
array( $saved_database_name )
);
// INFORMATION_SCHEMA.COLUMNS
$columns_table = $this->schema_builder->get_table_name( false, 'columns' );
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s SET TABLE_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $columns_table )
),
array( $saved_database_name )
);
// INFORMATION_SCHEMA.STATISTICS
$statistics_table = $this->schema_builder->get_table_name( false, 'statistics' );
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s SET TABLE_SCHEMA = ?, INDEX_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $statistics_table )
),
array( $saved_database_name, $saved_database_name )
);
// INFORMATION_SCHEMA.TABLE_CONSTRAINTS
$table_constraints_table = $this->schema_builder->get_table_name( false, 'table_constraints' );
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s SET TABLE_SCHEMA = ?, CONSTRAINT_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $table_constraints_table )
),
array( $saved_database_name, $saved_database_name )
);
// INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
$referential_constraints_table = $this->schema_builder->get_table_name( false, 'referential_constraints' );
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s SET CONSTRAINT_SCHEMA = ?, UNIQUE_CONSTRAINT_SCHEMA = ? WHERE CONSTRAINT_SCHEMA != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $referential_constraints_table )
),
array( $saved_database_name, $saved_database_name )
);
// INFORMATION_SCHEMA.KEY_COLUMN_USAGE
$key_column_usage_table = $this->schema_builder->get_table_name( false, 'key_column_usage' );
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s
SET
TABLE_SCHEMA = ?,
CONSTRAINT_SCHEMA = ?,
REFERENCED_TABLE_SCHEMA = CASE WHEN REFERENCED_TABLE_SCHEMA IS NULL THEN NULL ELSE ? END
WHERE TABLE_SCHEMA != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $key_column_usage_table )
),
array( $saved_database_name, $saved_database_name, $saved_database_name )
);
// INFORMATION_SCHEMA.CHECK_CONSTRAINTS
$check_constraints_table = $this->schema_builder->get_table_name( false, 'check_constraints' );
$this->driver->execute_sqlite_query(
sprintf(
"UPDATE %s SET CONSTRAINT_SCHEMA = ? WHERE CONSTRAINT_SCHEMA != 'information_schema'",
$this->driver->get_connection()->quote_identifier( $check_constraints_table )
),
array( $saved_database_name )
);
}
}
/**
* Save the current SQLite driver version.
*
* This method saves the current SQLite driver version to the database.
*/
private function save_current_driver_version(): void {
$this->driver->execute_sqlite_query(
sprintf(
'INSERT INTO %s (name, value) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET value = ?',
$this->driver->get_connection()->quote_identifier(
WP_PDO_MySQL_On_SQLite::GLOBAL_VARIABLES_TABLE_NAME
)
),
array(
WP_PDO_MySQL_On_SQLite::DRIVER_VERSION_VARIABLE_NAME,
SQLITE_DRIVER_VERSION,
SQLITE_DRIVER_VERSION,
)
);
}
}
@@ -0,0 +1,210 @@
<?php declare(strict_types = 1);
/*
* The SQLite connection uses PDO. Enable PDO function calls:
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
*/
/**
* SQLite connection.
*
* This class configures and encapsulates the connection to an SQLite database.
* It requires PDO with the SQLite driver, and currently, it is only a simple
* wrapper that leaks some of the PDO APIs (returns PDOStatement values, etc.).
* In the future, we may abstract it away from PDO and support SQLite3 as well.
*/
class WP_SQLite_Connection {
/**
* The default timeout in seconds for SQLite to wait for a writable lock.
*/
const DEFAULT_SQLITE_TIMEOUT = 10;
/**
* The supported SQLite journal modes.
*
* See: https://www.sqlite.org/pragma.html#pragma_journal_mode
*/
const SQLITE_JOURNAL_MODES = array(
'DELETE',
'TRUNCATE',
'PERSIST',
'MEMORY',
'WAL',
'OFF',
);
/**
* The PDO connection for SQLite.
*
* @var PDO
*/
private $pdo;
/**
* A query logger callback.
*
* @var callable(string, array): void
*/
private $query_logger;
/**
* Constructor.
*
* Set up an SQLite connection.
*
* @param array $options {
* An array of options.
*
* @type string|null $path Optional. SQLite database path.
* For in-memory database, use ':memory:'.
* Must be set when PDO instance is not provided.
* @type PDO|null $pdo Optional. PDO instance with SQLite connection.
* If not provided, a new PDO instance will be created.
* @type int|null $timeout Optional. SQLite timeout in seconds.
* The time to wait for a writable lock.
* @type string|null $journal_mode Optional. SQLite journal mode.
* }
*
* @throws InvalidArgumentException When some connection options are invalid.
* @throws PDOException When the driver initialization fails.
*/
public function __construct( array $options ) {
// Setup PDO connection.
if ( isset( $options['pdo'] ) && $options['pdo'] instanceof PDO ) {
$this->pdo = $options['pdo'];
} else {
if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) {
throw new InvalidArgumentException( 'Option "path" is required when "connection" is not provided.' );
}
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
$this->pdo = new $pdo_class( 'sqlite:' . $options['path'] );
}
// Throw exceptions on error.
$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
// Configure SQLite timeout.
if ( isset( $options['timeout'] ) && is_int( $options['timeout'] ) ) {
$timeout = $options['timeout'];
} else {
$timeout = self::DEFAULT_SQLITE_TIMEOUT;
}
$this->pdo->setAttribute( PDO::ATTR_TIMEOUT, $timeout );
// Configure SQLite journal mode.
$journal_mode = $options['journal_mode'] ?? null;
if ( $journal_mode && in_array( $journal_mode, self::SQLITE_JOURNAL_MODES, true ) ) {
$this->query( 'PRAGMA journal_mode = ' . $journal_mode );
}
}
/**
* Execute a query in SQLite.
*
* @param string $sql The query to execute.
* @param array $params The query parameters.
* @throws PDOException When the query execution fails.
* @return PDOStatement The PDO statement object.
*/
public function query( string $sql, array $params = array() ): PDOStatement {
if ( $this->query_logger ) {
( $this->query_logger )( $sql, $params );
}
$stmt = $this->pdo->prepare( $sql );
$stmt->execute( $params );
return $stmt;
}
/**
* Prepare a SQLite query for execution.
*
* @param string $sql The query to prepare.
* @return PDOStatement The prepared statement.
* @throws PDOException When the query preparation fails.
*/
public function prepare( string $sql ): PDOStatement {
if ( $this->query_logger ) {
( $this->query_logger )( $sql, array() );
}
return $this->pdo->prepare( $sql );
}
/**
* Returns the ID of the last inserted row.
*
* @return string The ID of the last inserted row.
*/
public function get_last_insert_id(): string {
return $this->pdo->lastInsertId();
}
/**
* Quote a value for use in a query.
*
* @param mixed $value The value to quote.
* @param int $type The type of the value.
* @return string The quoted value.
*/
public function quote( $value, int $type = PDO::PARAM_STR ): string {
return $this->pdo->quote( $value, $type );
}
/**
* Quote an SQLite identifier.
*
* Wraps the identifier in backticks and escapes backtick characters within.
*
* ---
*
* Quoted identifiers in SQLite are represented by string constants:
*
* A string constant is formed by enclosing the string in single quotes (').
* A single quote within the string can be encoded by putting two single
* quotes in a row - as in Pascal. C-style escapes using the backslash
* character are not supported because they are not standard SQL.
*
* See: https://www.sqlite.org/lang_expr.html#literal_values_constants_
*
* Although sparsely documented, this applies to backtick and double quoted
* string constants as well, so only the quote character needs to be escaped.
*
* For more details, see the grammar for SQLite table and column names:
*
* - https://github.com/sqlite/sqlite/blob/873fc5dff2a781251f2c9bd2c791a5fac45b7a2b/src/tokenize.c#L395-L419
* - https://github.com/sqlite/sqlite/blob/873fc5dff2a781251f2c9bd2c791a5fac45b7a2b/src/parse.y#L321-L338
*
* ---
*
* We use backtick quotes instead of the SQL standard double quotes, due to
* an SQLite quirk causing double-quoted strings to be accepted as literals:
*
* This misfeature means that a misspelled double-quoted identifier will
* be interpreted as a string literal, rather than generating an error.
*
* See: https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted
*
* @param string $unquoted_identifier The unquoted identifier value.
* @return string The quoted identifier value.
*/
public function quote_identifier( string $unquoted_identifier ): string {
return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`';
}
/**
* Get the PDO object.
*
* @return PDO
*/
public function get_pdo(): PDO {
return $this->pdo;
}
/**
* Set a logger for the queries.
*
* @param callable(string, array): void $logger A query logger callback.
*/
public function set_query_logger( callable $logger ): void {
$this->query_logger = $logger;
}
}
@@ -0,0 +1,33 @@
<?php
class WP_SQLite_Driver_Exception extends PDOException {
/**
* The SQLite driver that originated the exception.
*
* @var WP_PDO_MySQL_On_SQLite
*/
private $driver;
/**
* Constructor.
*
* @param WP_PDO_MySQL_On_SQLite $driver The SQLite driver that originated the exception.
* @param string $message The exception message.
* @param int|string $code The exception code. In PDO, it can be a string with value of SQLSTATE.
* @param Throwable|null $previous The previous throwable used for the exception chaining.
*/
public function __construct(
WP_PDO_MySQL_On_SQLite $driver,
string $message,
$code = 0,
?Throwable $previous = null
) {
parent::__construct( $message, 0, $previous );
$this->code = $code;
$this->driver = $driver;
}
public function getDriver(): WP_PDO_MySQL_On_SQLite {
return $this->driver;
}
}
@@ -0,0 +1,279 @@
<?php
/*
* The SQLite driver uses PDO. Enable PDO function calls:
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
*/
/**
* For back compatibility with dependencies that use their own loader scripts
* (e.g., WP CLI SQLite Command), ensure the new PDO-based classes are loaded.
*/
require_once __DIR__ . '/class-wp-pdo-mysql-on-sqlite.php';
require_once __DIR__ . '/class-wp-pdo-proxy-statement.php';
/**
* Deprecated: A proxy of the WP_PDO_MySQL_On_SQLite class preserving legacy API.
*
* This is a temporary class to preserve the legacy API for easier transition
* to the new PDO-based API, developed in the "WP_PDO_MySQL_On_SQLite" class.
*/
class WP_SQLite_Driver {
/**
* The SQLite engine version.
*
* This is a mysqli-like property that is needed to avoid a PHP warning in
* the WordPress health info. The "WP_Debug_Data::get_wp_database()" method
* calls "$wpdb->dbh->client_info" - a mysqli-specific abstraction leak.
*
* @TODO: This should be fixed in WordPress core.
*
* See:
* https://github.com/WordPress/wordpress-develop/blob/bcdca3f9925f1d3eca7b78d231837c0caf0c8c24/src/wp-admin/includes/class-wp-debug-data.php#L1579
*
* @var string
*/
public $client_info;
/**
* The MySQL-on-SQLite driver instance.
*
* @var WP_PDO_MySQL_On_SQLite
*/
private $mysql_on_sqlite_driver;
/**
* Results of the last emulated query.
*
* @var mixed
*/
private $last_result;
/**
* Constructor.
*
* Set up an SQLite connection and the MySQL-on-SQLite driver.
*
* @param WP_SQLite_Connection $connection A SQLite database connection.
* @param string $database The database name.
*
* @throws WP_SQLite_Driver_Exception When the driver initialization fails.
*/
public function __construct(
WP_SQLite_Connection $connection,
string $database,
int $mysql_version = 80038
) {
$this->mysql_on_sqlite_driver = new WP_PDO_MySQL_On_SQLite(
sprintf( 'mysql-on-sqlite:dbname=%s', $database ),
null,
null,
array(
'mysql_version' => $mysql_version,
'pdo' => $connection->get_pdo(),
)
);
$this->main_db_name = $database;
$this->client_info = $this->mysql_on_sqlite_driver->client_info;
$connection->get_pdo()->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true );
}
/**
* Get the SQLite connection instance.
*
* @return WP_SQLite_Connection
*/
public function get_connection(): WP_SQLite_Connection {
return $this->mysql_on_sqlite_driver->get_connection();
}
/**
* Get the version of the SQLite engine.
*
* @return string SQLite engine version as a string.
*/
public function get_sqlite_version(): string {
return $this->mysql_on_sqlite_driver->get_sqlite_version();
}
/**
* Get the SQLite driver version saved in the database.
*
* The saved driver version corresponds to the latest version of the SQLite
* driver that was used to initialize and configure the SQLite database.
*
* @return string SQLite driver version as a string.
* @throws PDOException When the query execution fails.
*/
public function get_saved_driver_version(): string {
return $this->mysql_on_sqlite_driver->get_saved_driver_version();
}
/**
* Check if a specific SQL mode is active.
*
* @param string $mode The SQL mode to check.
* @return bool True if the SQL mode is active, false otherwise.
*/
public function is_sql_mode_active( string $mode ): bool {
return $this->mysql_on_sqlite_driver->is_sql_mode_active( $mode );
}
/**
* Get the last executed MySQL query.
*
* @return string|null
*/
public function get_last_mysql_query(): ?string {
return $this->mysql_on_sqlite_driver->get_last_mysql_query();
}
/**
* Get SQLite queries executed for the last MySQL query.
*
* @return array{ sql: string, params: array }[]
*/
public function get_last_sqlite_queries(): array {
return $this->mysql_on_sqlite_driver->get_last_sqlite_queries();
}
/**
* Get the auto-increment value generated for the last query.
*
* @return int|string
*/
public function get_insert_id() {
return $this->mysql_on_sqlite_driver->get_insert_id();
}
/**
* @param string $query Full SQL statement string.
* @param int $fetch_mode PDO fetch mode. Default is PDO::FETCH_OBJ.
* @param array ...$fetch_mode_args Additional fetch mode arguments.
*
* @return mixed Return value, depending on the query type.
*
* @throws WP_SQLite_Driver_Exception When the query execution fails.
*/
public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mode_args ) {
$stmt = $this->mysql_on_sqlite_driver->query( $query, $fetch_mode, ...$fetch_mode_args );
if ( $stmt->columnCount() > 0 ) {
$this->last_result = $stmt->fetchAll( $fetch_mode );
} else {
$this->last_result = $stmt->rowCount();
}
return $this->last_result;
}
/**
* Tokenize a MySQL query and initialize a parser.
*
* @param string $query The MySQL query to parse.
* @return WP_MySQL_Parser A parser initialized for the MySQL query.
*/
public function create_parser( string $query ): WP_MySQL_Parser {
return $this->mysql_on_sqlite_driver->create_parser( $query );
}
/**
* Get results of the last query.
*
* @return mixed
*/
public function get_query_results() {
return $this->last_result;
}
/**
* Get return value of the last query() function call.
*
* @return mixed
*/
public function get_last_return_value() {
return $this->last_result;
}
/**
* Get the number of columns returned by the last emulated query.
*
* @return int
*/
public function get_last_column_count(): int {
return $this->mysql_on_sqlite_driver->get_last_column_count();
}
/**
* Get column metadata for results of the last emulated query.
*
* @return array
*/
public function get_last_column_meta(): array {
return $this->mysql_on_sqlite_driver->get_last_column_meta();
}
/**
* Execute a query in SQLite.
*
* @param string $sql The query to execute.
* @param array $params The query parameters.
* @throws PDOException When the query execution fails.
* @return PDOStatement The PDO statement object.
*/
public function execute_sqlite_query( string $sql, array $params = array() ): PDOStatement {
return $this->mysql_on_sqlite_driver->execute_sqlite_query( $sql, $params );
}
/**
* Begin a new transaction or nested transaction.
*/
public function beginTransaction(): void { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
$this->mysql_on_sqlite_driver->begin_transaction();
}
/**
* A temporary alias for back compatibility.
*
* @see self::beginTransaction()
*/
public function begin_transaction(): void {
$this->beginTransaction();
}
/**
* Commit the current transaction or nested transaction.
*/
public function commit(): void {
$this->mysql_on_sqlite_driver->commit();
}
/**
* Rollback the current transaction or nested transaction.
*/
public function rollback(): void {
$this->mysql_on_sqlite_driver->rollback();
}
/**
* Proxy also the private property "$main_db_name", as it is used in tests.
*/
public function __set( string $name, $value ): void {
if ( 'main_db_name' === $name ) {
$closure = function ( string $value ) {
$this->main_db_name = $value;
};
$closure->call( $this->mysql_on_sqlite_driver, $value );
}
}
/**
* Proxy also this private method, as it is used in tests.
*/
private function quote_mysql_utf8_string_literal( string $utf8_literal ): string {
$closure = function ( string $utf8_literal ) {
return $this->quote_mysql_utf8_string_literal( $utf8_literal );
};
return $closure->call( $this->mysql_on_sqlite_driver, $utf8_literal );
}
}
@@ -0,0 +1,152 @@
<?php
/**
* SQLite information schema exception.
*
* This class is used to represent errors that may occur when building
* the MySQL information schema for emulation in SQLite.
*/
class WP_SQLite_Information_Schema_Exception extends Exception {
// Information schema exception types.
const TYPE_DUPLICATE_TABLE_NAME = 'duplicate-table-name';
const TYPE_DUPLICATE_COLUMN_NAME = 'duplicate-column-name';
const TYPE_DUPLICATE_KEY_NAME = 'duplicate-key-name';
const TYPE_KEY_COLUMN_NOT_FOUND = 'key-column-not-found';
const TYPE_CONSTRAINT_DOES_NOT_EXIST = 'constraint-does-not-exist';
const TYPE_MULTIPLE_CONSTRAINTS_WITH_NAME = 'multiple-constraints-with-name';
/**
* The exception type.
*
* @var string
*/
private $type;
/**
* The data to be passed with the exception.
*
* @var array
*/
private $data;
/**
* Constructor.
*
* @param string $type The exception type.
* @param string $message The exception message.
* @param array $data The data to be passed with the exception.
* @param Throwable|null $previous The previous throwable used for the exception chaining.
*/
public function __construct(
string $type,
string $message,
array $data = array(),
?Throwable $previous = null
) {
parent::__construct( $message, 0, $previous );
$this->type = $type;
$this->data = $data;
}
/**
* Get the type of the exception.
*
* @return string The type of the exception.
*/
public function get_type(): string {
return $this->type;
}
/**
* Get the data associated with the exception.
*
* @return array The data associated with the exception.
*/
public function get_data(): array {
return $this->data;
}
/**
* Create a duplicate table name exception.
*
* @param string $table_name The name of the affected table.
* @return self The exception instance.
*/
public static function duplicate_table_name( string $table_name ): WP_SQLite_Information_Schema_Exception {
return new self(
self::TYPE_DUPLICATE_TABLE_NAME,
sprintf( "Table '%s' already exists.", $table_name ),
array( 'table_name' => $table_name )
);
}
/**
* Create a duplicate column name exception.
*
* @param string $column_name The name of the affected column.
* @return self The exception instance.
*/
public static function duplicate_column_name( string $column_name ): WP_SQLite_Information_Schema_Exception {
return new self(
self::TYPE_DUPLICATE_COLUMN_NAME,
sprintf( "Column '%s' already exists.", $column_name ),
array( 'column_name' => $column_name )
);
}
/**
* Create a duplicate key name exception.
*
* @param string $key_name The name of the affected key.
* @return self The exception instance.
*/
public static function duplicate_key_name( string $key_name ): WP_SQLite_Information_Schema_Exception {
return new self(
self::TYPE_DUPLICATE_KEY_NAME,
sprintf( "Key '%s' already exists.", $key_name ),
array( 'key_name' => $key_name )
);
}
/**
* Create a key column not found exception.
*
* @param string $column_name The name of the affected column.
* @return self The exception instance.
*/
public static function key_column_not_found( string $column_name ): WP_SQLite_Information_Schema_Exception {
return new self(
self::TYPE_KEY_COLUMN_NOT_FOUND,
sprintf( "Key column '%s' doesn't exist in table.", $column_name ),
array( 'column_name' => $column_name )
);
}
/**
* Create a constraint does not exist exception.
*
* @param string $name The name of the affected constraint.
* @return self The exception instance.
*/
public static function constraint_does_not_exist( string $name ): WP_SQLite_Information_Schema_Exception {
return new self(
self::TYPE_CONSTRAINT_DOES_NOT_EXIST,
sprintf( "Constraint '%s' does not exist.", $name ),
array( 'name' => $name )
);
}
/**
* Create a multiple constraints with name exception.
*
* @param string $name The name of the affected constraint.
* @return self The exception instance.
*/
public static function multiple_constraints_with_name( string $name ): WP_SQLite_Information_Schema_Exception {
return new self(
self::TYPE_MULTIPLE_CONSTRAINTS_WITH_NAME,
sprintf( "Table has multiple constraints with the name '%s'. Please use constraint specific 'DROP' clause.", $name ),
array( 'name' => $name )
);
}
}
@@ -0,0 +1,802 @@
<?php
/*
* The SQLite driver uses PDO. Enable PDO function calls:
* phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO
*/
/**
* SQLite information schema recconstructor for MySQL.
*
* This class checks and reconstructs the MySQL INFORMATION_SCHEMA data in SQLite
* when it becomes out of sync with the actual SQLite database schema.
*
* Currently, it reconstructs schema infromation for missing tables, and removes
* stale data for tables that no longer exist. When used with WordPress, it uses
* the "wp_get_db_schema()" function to reconstruct WordPress table information.
*/
class WP_SQLite_Information_Schema_Reconstructor {
/**
* The SQLite driver instance.
*
* @var WP_PDO_MySQL_On_SQLite
*/
private $driver;
/**
* An instance of the SQLite connection.
*
* @var WP_SQLite_Connection
*/
private $connection;
/**
* A service for managing MySQL INFORMATION_SCHEMA tables in SQLite.
*
* @var WP_SQLite_Information_Schema_Builder
*/
private $schema_builder;
/**
* Constructor.
*
* @param WP_PDO_MySQL_On_SQLite $driver The SQLite driver instance.
* @param WP_SQLite_Information_Schema_Builder $schema_builder The information schema builder instance.
*/
public function __construct(
$driver,
WP_SQLite_Information_Schema_Builder $schema_builder
) {
$this->driver = $driver;
$this->connection = $driver->get_connection();
$this->schema_builder = $schema_builder;
}
/**
* Ensure that the MySQL INFORMATION_SCHEMA data in SQLite is correct.
*
* This method checks if the MySQL INFORMATION_SCHEMA data in SQLite is correct,
* and if it is not, it will reconstruct missing data and remove stale values.
*/
public function ensure_correct_information_schema(): void {
$sqlite_tables = $this->get_sqlite_table_names();
$information_schema_tables = $this->get_information_schema_table_names();
// In WordPress, use "wp_get_db_schema()" to reconstruct WordPress tables.
$wp_tables = $this->get_wp_create_table_statements();
// Reconstruct information schema records for tables that don't have them.
foreach ( $sqlite_tables as $table ) {
if ( ! in_array( $table, $information_schema_tables, true ) ) {
if ( isset( $wp_tables[ $table ] ) ) {
// WordPress core table (as returned by "wp_get_db_schema()").
$ast = $wp_tables[ $table ];
} else {
// Other table (a WordPress plugin or unrelated to WordPress).
$sql = $this->generate_create_table_statement( $table );
$ast = $this->driver->create_parser( $sql )->parse();
if ( null === $ast ) {
throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' );
}
}
/*
* First, let's make sure we clean up all related data. This fixes
* partial data corruption, such as when a table record is missing,
* but some related column, index, or constraint records are stored.
*/
$this->record_drop_table( $table );
$this->schema_builder->record_create_table( $ast );
}
}
// Remove information schema records for tables that don't exist.
foreach ( $information_schema_tables as $table ) {
if ( ! in_array( $table, $sqlite_tables, true ) ) {
$this->record_drop_table( $table );
}
}
}
/**
* Record a DROP TABLE statement in the information schema.
*
* This removes a table record from the information schema, as well as all
* column, index, and constraint records that are related to the table.
*
* @param string $table_name The name of the table to drop.
*/
private function record_drop_table( string $table_name ): void {
$sql = sprintf( 'DROP TABLE %s', $this->connection->quote_identifier( $table_name ) ); // TODO: mysql quote
$ast = $this->driver->create_parser( $sql )->parse();
if ( null === $ast ) {
throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' );
}
$this->schema_builder->record_drop_table(
$ast->get_first_descendant_node( 'dropStatement' )
);
}
/**
* Get the names of all existing tables in the SQLite database.
*
* @return string[] The names of tables in the SQLite database.
*/
private function get_sqlite_table_names(): array {
return $this->driver->execute_sqlite_query(
"
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name != ?
AND name NOT LIKE ? ESCAPE '\'
AND name NOT LIKE ? ESCAPE '\'
ORDER BY name
",
array(
'_mysql_data_types_cache',
'sqlite\_%',
str_replace( '_', '\_', WP_PDO_MySQL_On_SQLite::RESERVED_PREFIX ) . '%',
)
)->fetchAll( PDO::FETCH_COLUMN );
}
/**
* Get the names of all tables recorded in the information schema.
*
* @return string[] The names of tables in the information schema.
*/
private function get_information_schema_table_names(): array {
$tables_table = $this->schema_builder->get_table_name( false, 'tables' );
return $this->driver->execute_sqlite_query(
sprintf(
'SELECT table_name FROM %s ORDER BY table_name',
$this->connection->quote_identifier( $tables_table )
)
)->fetchAll( PDO::FETCH_COLUMN );
}
/**
* Get a map of parsed CREATE TABLE statements for WordPress tables.
*
* When reconstructing the information schema data for WordPress tables, we
* can use the "wp_get_db_schema()" function to get accurate CREATE TABLE
* statements. This method parses the result of "wp_get_db_schema()" into
* an array of parsed CREATE TABLE statements indexed by the table names.
*
* @return array<string, WP_Parser_Node> The WordPress CREATE TABLE statements.
*/
private function get_wp_create_table_statements(): array {
// Bail out when not in a WordPress environment.
if ( ! defined( 'ABSPATH' ) ) {
return array();
}
/*
* In WP CLI, $wpdb may not be set. In that case, we can't load the schema.
* We need to bail out and use the standard non-WordPress-specific behavior.
*/
global $wpdb;
if ( ! isset( $wpdb ) ) {
// Outside of WP CLI, let's trigger a warning.
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
trigger_error( 'The $wpdb global is not initialized.', E_USER_WARNING );
}
return array();
}
// Ensure the "wp_get_db_schema()" function is defined.
if ( file_exists( ABSPATH . 'wp-admin/includes/schema.php' ) ) {
require_once ABSPATH . 'wp-admin/includes/schema.php';
}
if ( ! function_exists( 'wp_get_db_schema' ) ) {
throw new Exception( 'The "wp_get_db_schema()" function was not defined.' );
}
/*
* At this point, WPDB may not yet be initialized, as we're configuring
* the database connection. Let's only populate the table names using
* the "$table_prefix" global so we can get correct table names.
*/
global $table_prefix;
$wpdb->set_prefix( $table_prefix );
// Get schema for global tables.
$schema = wp_get_db_schema( 'global' );
// For multisite installs, add schema definitions for all sites.
if ( is_multisite() ) {
/*
* We need to use a database query over the "get_sites()" function,
* as WPDB may not yet initialized. Moreover, we need to get the IDs
* of all existing blogs, independent of any filters and actions that
* could possibly alter the results of a "get_sites()" call.
*/
$blog_ids = $this->driver->execute_sqlite_query(
sprintf(
'SELECT blog_id FROM %s',
$this->connection->quote_identifier( $wpdb->blogs )
)
)->fetchAll( PDO::FETCH_COLUMN );
foreach ( $blog_ids as $blog_id ) {
$schema .= wp_get_db_schema( 'blog', (int) $blog_id );
}
} else {
// For single site installs, add schema for the main site.
$schema .= wp_get_db_schema( 'blog' );
}
// Parse the schema.
$parser = $this->driver->create_parser( $schema );
$wp_tables = array();
while ( $parser->next_query() ) {
$ast = $parser->get_query_ast();
if ( null === $ast ) {
throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' );
}
$create_node = $ast->get_first_descendant_node( 'createStatement' );
if ( $create_node && $create_node->has_child_node( 'createTable' ) ) {
$name_node = $create_node->get_first_descendant_node( 'tableName' );
$name = $this->unquote_mysql_identifier(
substr( $schema, $name_node->get_start(), $name_node->get_length() )
);
$wp_tables[ $name ] = $create_node;
}
}
return $wp_tables;
}
/**
* Generate a MySQL CREATE TABLE statement from an SQLite table definition.
*
* @param string $table_name The name of the table.
* @return string The CREATE TABLE statement.
*/
private function generate_create_table_statement( string $table_name ): string {
// Columns.
$columns = $this->driver->execute_sqlite_query(
sprintf(
'PRAGMA table_xinfo(%s)',
$this->connection->quote_identifier( $table_name )
)
)->fetchAll( PDO::FETCH_ASSOC );
$definitions = array();
$column_types = array();
foreach ( $columns as $column ) {
$mysql_type = $this->get_cached_mysql_data_type( $table_name, $column['name'] );
if ( null === $mysql_type ) {
$mysql_type = $this->get_mysql_column_type( $column['type'] );
}
$definitions[] = $this->generate_column_definition( $table_name, $column );
$column_types[ $column['name'] ] = $mysql_type;
}
// Primary key.
$pk_columns = array();
foreach ( $columns as $column ) {
// A position of the column in the primary key, starting from index 1.
// A value of 0 means that the column is not part of the primary key.
$pk_position = (int) $column['pk'];
if ( 0 !== $pk_position ) {
$pk_columns[ $pk_position ] = $column['name'];
}
}
// Sort the columns by their position in the primary key.
ksort( $pk_columns );
if ( count( $pk_columns ) > 0 ) {
$quoted_pk_columns = array();
foreach ( $pk_columns as $pk_column ) {
$quoted_pk_columns[] = $this->connection->quote_identifier( $pk_column );
}
$definitions[] = sprintf( 'PRIMARY KEY (%s)', implode( ', ', $quoted_pk_columns ) );
}
// Indexes and keys.
$keys = $this->driver->execute_sqlite_query(
sprintf(
'PRAGMA index_list(%s)',
$this->connection->quote_identifier( $table_name )
)
)->fetchAll( PDO::FETCH_ASSOC );
foreach ( $keys as $key ) {
// Skip the internal index that SQLite may create for a primary key.
// In MySQL, no explicit index needs to be defined for a primary key.
if ( 'pk' === $key['origin'] ) {
continue;
}
$definitions[] = $this->generate_key_definition( $table_name, $key, $column_types );
}
return sprintf(
"CREATE TABLE %s (\n %s\n)",
$this->connection->quote_identifier( $table_name ),
implode( ",\n ", $definitions )
);
}
/**
* Generate a MySQL column definition from an SQLite column information.
*
* This method generates a MySQL column definition from SQLite column data.
*
* @param string $table_name The name of the table.
* @param array $column_info The SQLite column information.
* @return string The MySQL column definition.
*/
private function generate_column_definition( string $table_name, array $column_info ): string {
$definition = array();
$definition[] = $this->connection->quote_identifier( $column_info['name'] );
// Data type.
$mysql_type = $this->get_cached_mysql_data_type( $table_name, $column_info['name'] );
if ( null === $mysql_type ) {
$mysql_type = $this->get_mysql_column_type( $column_info['type'] );
}
/**
* Correct some column types based on their default values:
* 1. In MySQL, non-datetime columns can't have a timestamp default.
* Let's use DATETIME when default is set to CURRENT_TIMESTAMP.
* 2. In MySQL, TEXT and BLOB columns can't have a default value.
* Let's use VARCHAR(65535) and VARBINARY(65535) when default is set.
*/
$default = $this->generate_column_default( $mysql_type, $column_info['dflt_value'] );
if ( 'CURRENT_TIMESTAMP' === $default ) {
$mysql_type = 'datetime';
} elseif ( 'text' === $mysql_type && null !== $default ) {
$mysql_type = 'varchar(65535)';
} elseif ( 'blob' === $mysql_type && null !== $default ) {
$mysql_type = 'varbinary(65535)';
}
$definition[] = $mysql_type;
// NULL/NOT NULL.
if ( '1' === $column_info['notnull'] ) {
$definition[] = 'NOT NULL';
}
// Auto increment.
$is_auto_increment = false;
if ( '0' !== $column_info['pk'] ) {
$is_auto_increment = $this->driver->execute_sqlite_query(
'SELECT 1 FROM sqlite_master WHERE tbl_name = ? AND sql LIKE ?',
array( $table_name, '%AUTOINCREMENT%' )
)->fetchColumn();
if ( $is_auto_increment ) {
$definition[] = 'AUTO_INCREMENT';
}
}
// Default value.
if ( null !== $default && ! $is_auto_increment ) {
$definition[] = 'DEFAULT ' . $default;
}
return implode( ' ', $definition );
}
/**
* Generate a MySQL key definition from an SQLite key information.
*
* This method generates a MySQL key definition from SQLite key data.
*
* @param string $table_name The name of the table.
* @param array $key_info The SQLite key information.
* @param array $column_types The MySQL data types of the columns.
* @return string The MySQL key definition.
*/
private function generate_key_definition( string $table_name, array $key_info, array $column_types ): string {
$definition = array();
// Key type.
$cached_type = $this->get_cached_mysql_data_type( $table_name, $key_info['name'] );
if ( 'FULLTEXT' === $cached_type ) {
$definition[] = 'FULLTEXT KEY';
} elseif ( 'SPATIAL' === $cached_type ) {
$definition[] = 'SPATIAL KEY';
} elseif ( 'UNIQUE' === $cached_type || '1' === $key_info['unique'] ) {
$definition[] = 'UNIQUE KEY';
} else {
$definition[] = 'KEY';
}
// Key name.
$name = $key_info['name'];
/*
* The SQLite driver prefixes index names with "{$table_name}__" to avoid
* naming conflicts among tables in SQLite. We need to remove the prefix.
*/
if ( str_starts_with( $name, "{$table_name}__" ) ) {
$name = substr( $name, strlen( "{$table_name}__" ) );
}
/**
* SQLite creates automatic internal indexes for primary and unique keys,
* naming them in format "sqlite_autoindex_{$table_name}_{$index_id}".
* For these internal indexes, we need to skip their name, so that in
* the generated MySQL definition, they follow implicit MySQL naming.
*/
if ( ! str_starts_with( $name, 'sqlite_autoindex_' ) ) {
$definition[] = $this->connection->quote_identifier( $name );
}
// Key columns.
$key_columns = $this->driver->execute_sqlite_query(
sprintf(
'PRAGMA index_info(%s)',
$this->connection->quote_identifier( $key_info['name'] )
)
)->fetchAll( PDO::FETCH_ASSOC );
$cols = array();
foreach ( $key_columns as $column ) {
/*
* Extract type and length from column data type definition.
*
* This is required when the column data type is inferred from the
* '_mysql_data_types_cache' table, which stores the data type in
* the format "type(length)", such as "varchar(255)".
*/
$max_prefix_length = 100;
$type = strtolower( $column_types[ $column['name'] ] );
$parts = explode( '(', $type );
$column_type = $parts[0];
$column_length = isset( $parts[1] ) ? (int) $parts[1] : null;
/*
* Add an index column prefix length, if needed.
*
* This is required for "text" and "blob" types for columns inferred
* directly from the SQLite schema, and for the following types for
* columns inferred from the '_mysql_data_types_cache' table:
* char, varchar
* text, tinytext, mediumtext, longtext
* blob, tinyblob, mediumblob, longblob
* varbinary
*/
if (
str_ends_with( $column_type, 'char' )
|| str_ends_with( $column_type, 'text' )
|| str_ends_with( $column_type, 'blob' )
|| str_starts_with( $column_type, 'var' )
) {
$cols[] = sprintf(
'%s(%d)',
$this->connection->quote_identifier( $column['name'] ),
min( $column_length ?? $max_prefix_length, $max_prefix_length )
);
} else {
$cols[] = $this->connection->quote_identifier( $column['name'] );
}
}
$definition[] = '(' . implode( ', ', $cols ) . ')';
return implode( ' ', $definition );
}
/**
* Generate a MySQL default value from an SQLite default value.
*
* @param string $mysql_type The MySQL data type of the column.
* @param string|null $default_value The default value of the SQLite column.
* @return string|null The default value, or null if the column has no default value.
*/
private function generate_column_default( string $mysql_type, ?string $default_value ): ?string {
if ( null === $default_value || '' === $default_value ) {
return null;
}
$mysql_type = strtolower( $mysql_type );
if ( str_starts_with( $mysql_type, 'bit' ) ) {
// BIT columns are stored as INTEGER in SQLite.
return "b'" . decbin( (int) $default_value ) . "'";
}
/*
* In MySQL, geometry columns can't have a default value.
*
* Geometry columns are saved as TEXT in SQLite, and in an older version
* of the SQLite driver, TEXT columns were assigned a default value of ''.
*/
if ( 'geomcollection' === $mysql_type || 'geometrycollection' === $mysql_type ) {
return null;
}
/*
* In MySQL, date/time columns can't have a default value of ''.
*
* Date/time columns are saved as TEXT in SQLite, and in an older version
* of the SQLite driver, TEXT columns were assigned a default value of ''.
*/
if (
"''" === $default_value
&& in_array( $mysql_type, array( 'datetime', 'date', 'time', 'timestamp', 'year' ), true )
) {
return null;
}
/**
* Convert SQLite default values to MySQL default values.
*
* See:
* - https://www.sqlite.org/syntax/column-constraint.html
* - https://www.sqlite.org/syntax/literal-value.html
* - https://www.sqlite.org/lang_expr.html#literal_values_constants_
*/
// Quoted string literal. E.g.: 'abc', "abc", `abc`
$first_byte = $default_value[0] ?? null;
if ( '"' === $first_byte || "'" === $first_byte || '`' === $first_byte ) {
$value = substr( $default_value, 1, -1 );
$value = str_replace( $first_byte . $first_byte, $first_byte, $value );
return $this->quote_mysql_utf8_string_literal( $value );
}
// Normalize the default value for easier comparison.
$uppercase_default_value = strtoupper( $default_value );
// NULL, TRUE, FALSE.
if ( 'NULL' === $uppercase_default_value ) {
// DEFAULT NULL is the same as no default value.
return null;
} elseif ( 'TRUE' === $uppercase_default_value ) {
return '1';
} elseif ( 'FALSE' === $uppercase_default_value ) {
return '0';
}
// Date/time values.
if ( 'CURRENT_TIMESTAMP' === $uppercase_default_value ) {
return 'CURRENT_TIMESTAMP';
} elseif ( 'CURRENT_DATE' === $uppercase_default_value ) {
return null; // Not supported in MySQL.
} elseif ( 'CURRENT_TIME' === $uppercase_default_value ) {
return null; // Not supported in MySQL.
}
// SQLite supports underscores in all numeric literals.
$no_underscore_default_value = str_replace( '_', '', $default_value );
// Numeric literals. E.g.: 123, 1.23, -1.23, 1e3, 1.2e-3
if ( is_numeric( $no_underscore_default_value ) ) {
return $no_underscore_default_value;
}
// HEX literals (numeric). E.g.: 0x1a2f, 0X1A2F
$value = filter_var( $no_underscore_default_value, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX );
if ( false !== $value ) {
return $value;
}
// BLOB literals (string). E.g.: x'1a2f', X'1A2F'
// Checking the prefix is enough as SQLite doesn't allow malformed values.
if ( str_starts_with( $uppercase_default_value, "X'" ) ) {
// Convert the hex string to ASCII bytes.
return "'" . pack( 'H*', substr( $default_value, 2, -1 ) ) . "'";
}
// Unquoted string literal. E.g.: abc
return $this->quote_mysql_utf8_string_literal( $default_value );
}
/**
* Get a MySQL column or index data type from legacy data types cache table.
*
* This method retrieves MySQL column or index data types from a special table
* that was used by an old version of the SQLite driver and that is otherwise
* no longer needed. This is more precise than direct inference from SQLite.
*
* For columns, it returns full column type, including prefix length, e.g.:
* int(11), bigint(20) unsigned, varchar(255), longtext
*
* For indexes, it returns one of:
* KEY, PRIMARY, UNIQUE, FULLTEXT, SPATIAL
*
* @param string $table_name The table name.
* @param string $column_or_index_name The column or index name.
* @return string|null The MySQL definition, or null when not found.
*/
private function get_cached_mysql_data_type( string $table_name, string $column_or_index_name ): ?string {
try {
$mysql_type = $this->driver->execute_sqlite_query(
'SELECT mysql_type FROM _mysql_data_types_cache
WHERE `table` = ? COLLATE NOCASE
AND (
-- The old SQLite driver stored the MySQL data types in multiple
-- formats - lowercase, uppercase, and, sometimes, with backticks.
column_or_index = ? COLLATE NOCASE
OR column_or_index = ? COLLATE NOCASE
)',
array( $table_name, $column_or_index_name, "`$column_or_index_name`" )
)->fetchColumn();
} catch ( PDOException $e ) {
if ( str_contains( $e->getMessage(), 'no such table' ) ) {
return null;
}
throw $e;
}
if ( false === $mysql_type ) {
return null;
}
/**
* Check whether the stored type value is a valid MySQL column type.
*
* Some older versions of the legacy SQLite driver might have stored
* invalid MySQL column types in some scenarios:
*
* 1. Before https://github.com/WordPress/sqlite-database-integration/pull/126,
* the legacy SQLite driver incorrectly stored MySQL column types
* for columns with multiple type arguments.
*
* E.g., a column definition like "col_name decimal(26, 8)" would
* be stored with invalid type "decimal(26,".
*
* 2. Before https://github.com/WordPress/sqlite-database-integration/commit/b5a9fbaed4d0d843f792aaa959e3d00f193ff1ee
* (see also https://github.com/Automattic/sqlite-database-integration/pull/2),
* the legacy SQLite driver incorrectly recognized indexes on columns
* with type keywords as additional table column definitions.
*
* E.g., an index definition like "KEY timestamp (timestamp)" would
* be stored as column "KEY" with invalid type "timestamp(timestamp)".
*
* To address these issues, we need to check whether the stored type looks
* like a valid MySQL column type definition.
*/
$open_par_index = strpos( $mysql_type, '(' );
$close_par_index = strpos( $mysql_type, ')' );
if ( false !== $open_par_index ) {
$end = false !== $close_par_index ? $close_par_index : strlen( $mysql_type );
$parts = explode( '(', substr( $mysql_type, 0, $end ) );
$type = strtolower( trim( $parts[0] ) );
$args = array();
foreach ( explode( ',', $parts[1] ) as $arg ) {
$args[] = strtolower( trim( $arg ) );
}
// WooCommerce uses decimal(26,8), decimal(19,4), and decimal(3,2)
// column types, so we can can fix the invalid column definitions.
$looks_like_wc_table = str_contains( $table_name, 'wc_' ) || str_contains( $table_name, 'woocommerce_' );
$is_invalid_decimal = 'decimal' === $type && count( $args ) === 2 && '' === $args[1];
if ( $looks_like_wc_table && $is_invalid_decimal ) {
if ( '26' === $args[0] ) {
// Fix "decimal(26,".
return 'decimal(26,8)';
} elseif ( '19' === $args[0] ) {
// Fix "decimal(19,".
return 'decimal(19,4)';
} elseif ( '3' === $args[0] ) {
// Fix "decimal(3,".
return 'decimal(3,2)';
}
}
// Only numeric arguments are allowed for MySQL column types.
// This handles the incorrectly stored index definition case.
foreach ( $args as $arg ) {
if ( ! is_numeric( $arg ) ) {
return null;
}
}
// If there is no closing parenthesis, the type is invalid.
if ( false === $close_par_index ) {
return null;
}
}
// Normalize index type for backward compatibility. Some older versions
// of the SQLite driver stored index types with a " KEY" suffix, e.g.,
// "PRIMARY KEY" or "UNIQUE KEY". More recent versions omit the suffix.
if ( str_ends_with( $mysql_type, ' KEY' ) ) {
$mysql_type = substr( $mysql_type, 0, strlen( $mysql_type ) - strlen( ' KEY' ) );
}
return $mysql_type;
}
/**
* Get a MySQL column type from an SQLite column type.
*
* This method converts an SQLite column type to a MySQL column type as per
* the SQLite column type affinity rules:
* https://sqlite.org/datatype3.html#determination_of_column_affinity
*
* @param string $column_type The SQLite column type.
* @return string The MySQL column type.
*/
private function get_mysql_column_type( string $column_type ): string {
$type = strtoupper( $column_type );
/*
* Following the rules of column affinity:
* https://sqlite.org/datatype3.html#determination_of_column_affinity
*/
// 1. If the declared type contains the string "INT" then it is assigned
// INTEGER affinity.
if ( str_contains( $type, 'INT' ) ) {
return 'int';
}
// 2. If the declared type of the column contains any of the strings
// "CHAR", "CLOB", or "TEXT" then that column has TEXT affinity.
if ( str_contains( $type, 'TEXT' ) || str_contains( $type, 'CHAR' ) || str_contains( $type, 'CLOB' ) ) {
return 'text';
}
// 3. If the declared type for a column contains the string "BLOB" or
// if no type is specified then the column has affinity BLOB.
if ( str_contains( $type, 'BLOB' ) || '' === $type ) {
return 'blob';
}
// 4. If the declared type for a column contains any of the strings
// "REAL", "FLOA", or "DOUB" then the column has REAL affinity.
if ( str_contains( $type, 'REAL' ) || str_contains( $type, 'FLOA' ) ) {
return 'float';
}
if ( str_contains( $type, 'DOUB' ) ) {
return 'double';
}
/**
* 5. Otherwise, the affinity is NUMERIC.
*
* While SQLite defaults to a NUMERIC column affinity, it's better to use
* TEXT in this case, because numeric SQLite columns in non-strict tables
* can contain any text data as well, when it is not a well-formed number.
*
* See: https://sqlite.org/datatype3.html#type_affinity
*/
return 'text';
}
/**
* Format a MySQL UTF-8 string literal for output in a CREATE TABLE statement.
*
* See WP_PDO_MySQL_On_SQLite::quote_mysql_utf8_string_literal().
*
* TODO: This is a copy of WP_PDO_MySQL_On_SQLite::quote_mysql_utf8_string_literal().
* We may consider extracing it to reusable MySQL helpers.
*
* @param string $utf8_literal The UTF-8 string literal to escape.
* @return string The escaped string literal.
*/
private function quote_mysql_utf8_string_literal( string $utf8_literal ): string {
$backslash = chr( 92 );
$replacements = array(
"'" => "''", // A single quote character (').
$backslash => $backslash . $backslash, // A backslash character (\).
chr( 0 ) => $backslash . '0', // An ASCII NULL character (\0).
chr( 10 ) => $backslash . 'n', // A newline (linefeed) character (\n).
chr( 13 ) => $backslash . 'r', // A carriage return character (\r).
);
return "'" . strtr( $utf8_literal, $replacements ) . "'";
}
/**
* Unquote a quoted MySQL identifier.
*
* Remove bounding quotes and replace escaped quotes with their values.
*
* @param string $quoted_identifier The quoted identifier value.
* @return string The unquoted identifier value.
*/
private function unquote_mysql_identifier( string $quoted_identifier ): string {
$first_byte = $quoted_identifier[0] ?? null;
if ( '"' === $first_byte || '`' === $first_byte ) {
$unquoted = substr( $quoted_identifier, 1, -1 );
return str_replace( $first_byte . $first_byte, $first_byte, $unquoted );
}
return $quoted_identifier;
}
}
@@ -0,0 +1,987 @@
<?php
/**
* Custom functions for the SQLite implementation.
*
* @package wp-sqlite-integration
* @since 1.0.0
*/
/**
* This class defines user defined functions(UDFs) for PDO library.
*
* These functions replace those used in the SQL statement with the PHP functions.
*
* Usage:
*
* <code>
* new WP_SQLite_PDO_User_Defined_Functions(ref_to_pdo_obj);
* </code>
*
* This automatically enables ref_to_pdo_obj to replace the function in the SQL statement
* to the ones defined here.
*/
class WP_SQLite_PDO_User_Defined_Functions {
/**
* Registers the user defined functions for SQLite to a PDO instance.
* The functions are registered using PDO::sqliteCreateFunction().
*
* @param PDO|PDO\SQLite $pdo The PDO object.
*/
public static function register_for( $pdo ): self {
$instance = new self();
foreach ( $instance->functions as $f => $t ) {
if ( $pdo instanceof PDO\SQLite ) {
$pdo->createFunction( $f, array( $instance, $t ) );
} else {
$pdo->sqliteCreateFunction( $f, array( $instance, $t ) );
}
}
return $instance;
}
/**
* Array to define MySQL function => function defined with PHP.
*
* Replaced functions must be public.
*
* @var array
*/
private $functions = array(
'throw' => 'throw',
'month' => 'month',
'monthnum' => 'month',
'year' => 'year',
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'second' => 'second',
'week' => 'week',
'weekday' => 'weekday',
'dayofweek' => 'dayofweek',
'dayofmonth' => 'dayofmonth',
'unix_timestamp' => 'unix_timestamp',
'now' => 'now',
'md5' => 'md5',
'curdate' => 'curdate',
'rand' => 'rand',
'from_unixtime' => 'from_unixtime',
'localtime' => 'now',
'localtimestamp' => 'now',
'isnull' => 'isnull',
'if' => '_if',
'regexp' => 'regexp',
'field' => 'field',
'log' => 'log',
'least' => 'least',
'greatest' => 'greatest',
'get_lock' => 'get_lock',
'release_lock' => 'release_lock',
'ucase' => 'ucase',
'lcase' => 'lcase',
'unhex' => 'unhex',
'from_base64' => 'from_base64',
'to_base64' => 'to_base64',
'inet_ntoa' => 'inet_ntoa',
'inet_aton' => 'inet_aton',
'datediff' => 'datediff',
'locate' => 'locate',
'utc_date' => 'utc_date',
'utc_time' => 'utc_time',
'utc_timestamp' => 'utc_timestamp',
'version' => 'version',
// Internal helper functions.
'_helper_like_to_glob_pattern' => '_helper_like_to_glob_pattern',
);
/**
* First element of the RAND(N) LCG state (the value the output is derived from).
*
* @var int|null
*/
private $rand_seed1 = null;
/**
* Second element of the RAND(N) LCG state (the paired value used in the recurrence).
*
* @var int|null
*/
private $rand_seed2 = null;
/**
* Last seed value passed to RAND(N) in the current statement.
*
* Used to detect whether the rand sequence is advancing with the same seed
* (e.g. "SELECT RAND(3) FROM t"), or reseeding (starting a new sequence).
*
* @var int|null
*/
private $rand_last_seed = null;
/**
* Clear any per-statement state held by the UDFs.
*/
public function flush(): void {
$this->rand_seed1 = null;
$this->rand_seed2 = null;
$this->rand_last_seed = null;
}
/**
* A helper function to throw an error from SQLite expressions.
*
* @param string $message The error message.
*
* @throws Exception The error message.
* @return void
*/
public function throw( $message ): void {
throw new Exception( $message );
}
/**
* Method to return the unix timestamp.
*
* Used without an argument, it returns PHP time() function (total seconds passed
* from '1970-01-01 00:00:00' GMT). Used with the argument, it changes the value
* to the timestamp.
*
* @param string $field Representing the date formatted as '0000-00-00 00:00:00'.
*
* @return number of unsigned integer
*/
public function unix_timestamp( $field = null ) {
return is_null( $field ) ? time() : strtotime( $field );
}
/**
* Method to emulate MySQL FROM_UNIXTIME() function.
*
* @param int $field The unix timestamp.
* @param string $format Indicate the way of formatting(optional).
*
* @return string
*/
public function from_unixtime( $field, $format = null ) {
// Convert to ISO time.
$date = gmdate( 'Y-m-d H:i:s', $field );
return is_null( $format ) ? $date : $this->dateformat( $date, $format );
}
/**
* Method to emulate MySQL NOW() function.
*
* @return string representing current time formatted as '0000-00-00 00:00:00'.
*/
public function now() {
return gmdate( 'Y-m-d H:i:s' );
}
/**
* Method to emulate MySQL CURDATE() function.
*
* @return string representing current time formatted as '0000-00-00'.
*/
public function curdate() {
return gmdate( 'Y-m-d' );
}
/**
* Method to emulate MySQL MD5() function.
*
* @param string $field The string to be hashed.
*
* @return string of the md5 hash value of the argument.
*/
public function md5( $field ) {
return md5( $field );
}
/**
* Method to emulate MySQL's seeded RAND(N) function.
*
* Implements MySQL's deterministic LCG (Linear Congruential Generator),
* producing bit-exact output for a given seed.
*
* Known divergences from MySQL:
*
* 1. In MySQL, RAND(N) behaves differently depending on whether the seed
* is constant expression or varies per invocation:
* - Constant seed (e.g. "SELECT RAND(3) FROM t"):
* LCG is initialized once per statement and advanced for each row.
* - Non-constant seed (e.g. "SELECT RAND(col) FROM t"):
* LCG is initialized for every row with its seed value.
*
* A SQLite UDF cannot tell whether the seed expression is constant, so
* we just compare the seed against its last value. This diverges from
* MySQL in rare cases, and we can consider improving it in the future.
*
* 2. The LCG state is shared across call sites in the same query, so
* "SELECT RAND(1), RAND(1)" yields different results here than in MySQL.
* This is a rare edge case that we can consider improving in the future.
*
* Unseeded RAND() never reaches this function. The AST driver translates it
* directly to a more efficient SQLite-native expression.
*
* @param int|float|string|null $seed Seed value.
*
* @return float A value in [0, 1).
*/
public function rand( $seed ) {
// Requires 64-bit PHP. Seed * 0x10000001 can exceed PHP_INT_MAX on 32-bit.
$max_value = 0x3FFFFFFF;
if ( null === $seed ) {
// MySQL treats NULL seed as 0.
$seed = 0;
} elseif ( ! is_int( $seed ) ) {
/*
* MySQL rounds float values and numeric strings take the same path.
* Reduce the value to a 32-bit range using "fmod" to avoid firing
* the "out-of-range float to int" cast deprecation on PHP 8.1+.
*/
$seed = (int) fmod( round( (float) $seed, 0, PHP_ROUND_HALF_EVEN ), 0x100000000 );
}
// Initialize MySQL's internal 30-bit seeds.
if ( $seed !== $this->rand_last_seed ) {
/*
* MySQL casts to uint32, and the intermediate results wrap at 32-bit
* unsigned boundaries. We emulate this with & 0xFFFFFFFF masks.
*/
$seed_u32 = $seed & 0xFFFFFFFF;
$this->rand_seed1 = ( ( $seed_u32 * 0x10001 + 55555555 ) & 0xFFFFFFFF ) % $max_value;
$this->rand_seed2 = ( ( $seed_u32 * 0x10000001 ) & 0xFFFFFFFF ) % $max_value;
$this->rand_last_seed = $seed;
}
/*
* MySQL's LCG recurrence:
* seed1 = (seed1 * 3 + seed2) % max_value
* seed2 = (seed1 + seed2 + 33) % max_value
*
* Note that seed1 is updated first and the new value is used for seed2.
*/
$this->rand_seed1 = ( $this->rand_seed1 * 3 + $this->rand_seed2 ) % $max_value;
$this->rand_seed2 = ( $this->rand_seed1 + $this->rand_seed2 + 33 ) % $max_value;
return (float) $this->rand_seed1 / (float) $max_value;
}
/**
* Method to emulate MySQL DATEFORMAT() function.
*
* @param string $date Formatted as '0000-00-00' or datetime as '0000-00-00 00:00:00'.
* @param string $format The string format.
*
* @return string formatted according to $format
*/
public function dateformat( $date, $format ) {
$mysql_php_date_formats = array(
'%a' => 'D',
'%b' => 'M',
'%c' => 'n',
'%D' => 'jS',
'%d' => 'd',
'%e' => 'j',
'%H' => 'H',
'%h' => 'h',
'%I' => 'h',
'%i' => 'i',
'%j' => 'z',
'%k' => 'G',
'%l' => 'g',
'%M' => 'F',
'%m' => 'm',
'%p' => 'A',
'%r' => 'h:i:s A',
'%S' => 's',
'%s' => 's',
'%T' => 'H:i:s',
'%U' => 'W',
'%u' => 'W',
'%V' => 'W',
'%v' => 'W',
'%W' => 'l',
'%w' => 'w',
'%X' => 'Y',
'%x' => 'o',
'%Y' => 'Y',
'%y' => 'y',
);
$time = strtotime( $date );
$format = strtr( $format, $mysql_php_date_formats );
return gmdate( $format, $time );
}
/**
* Method to extract the month value from the date.
*
* @param string $field Representing the date formatted as 0000-00-00.
*
* @return string Representing the number of the month between 1 and 12.
*/
public function month( $field ) {
/*
* MySQL returns 0 for MONTH('0000-00-00') and for dates with
* zero month parts like '2020-00-15'. PHP's strtotime() can't
* parse these, so we extract the month directly from the string.
*/
if ( preg_match( '/^\d{4}-(\d{2})/', $field, $matches ) ) {
return intval( $matches[1] );
}
/*
* From https://www.php.net/manual/en/datetime.format.php:
*
* n - Numeric representation of a month, without leading zeros.
* 1 through 12
*/
return intval( gmdate( 'n', strtotime( $field ) ) );
}
/**
* Method to extract the year value from the date.
*
* @param string $field Representing the date formatted as 0000-00-00.
*
* @return string Representing the number of the year.
*/
public function year( $field ) {
/*
* MySQL returns 0 for YEAR('0000-00-00'). PHP's strtotime()
* can't parse zero dates, so we extract the year directly.
*/
if ( preg_match( '/^(\d{4})-\d{2}/', $field, $matches ) ) {
return intval( $matches[1] );
}
/*
* From https://www.php.net/manual/en/datetime.format.php:
*
* Y - A full numeric representation of a year, 4 digits.
*/
return intval( gmdate( 'Y', strtotime( $field ) ) );
}
/**
* Method to extract the day value from the date.
*
* @param string $field Representing the date formatted as 0000-00-00.
*
* @return string Representing the number of the day of the month from 1 and 31.
*/
public function day( $field ) {
/*
* MySQL returns 0 for DAY('0000-00-00') and for dates with
* zero day parts like '2020-01-00'. PHP's strtotime() can't
* parse these, so we extract the day directly from the string.
*/
if ( preg_match( '/^\d{4}-\d{2}-(\d{2})/', $field, $matches ) ) {
return intval( $matches[1] );
}
/*
* From https://www.php.net/manual/en/datetime.format.php:
*
* j - Day of the month without leading zeros.
* 1 to 31.
*/
return intval( gmdate( 'j', strtotime( $field ) ) );
}
/**
* Method to emulate MySQL SECOND() function.
*
* @see https://www.php.net/manual/en/datetime.format.php
*
* @param string $field Representing the time formatted as '00:00:00'.
*
* @return number Unsigned integer
*/
public function second( $field ) {
/*
* From https://www.php.net/manual/en/datetime.format.php:
*
* s - Seconds, with leading zeros (00 to 59)
*/
return intval( gmdate( 's', strtotime( $field ) ) );
}
/**
* Method to emulate MySQL MINUTE() function.
*
* @param string $field Representing the time formatted as '00:00:00'.
*
* @return int
*/
public function minute( $field ) {
/*
* From https://www.php.net/manual/en/datetime.format.php:
*
* i - Minutes with leading zeros.
* 00 to 59.
*/
return intval( gmdate( 'i', strtotime( $field ) ) );
}
/**
* Method to emulate MySQL HOUR() function.
*
* Returns the hour for time, in 24-hour format, from 0 to 23.
* Importantly, midnight is 0, not 24.
*
* @param string $time Representing the time formatted, like '14:08:12'.
*
* @return int
*/
public function hour( $time ) {
/*
* From https://www.php.net/manual/en/datetime.format.php:
*
* H 24-hour format of an hour with leading zeros.
* 00 through 23.
*/
return intval( gmdate( 'H', strtotime( $time ) ) );
}
/**
* Covers MySQL WEEK() function.
*
* Always assumes $mode = 1.
*
* @TODO: Support other modes.
*
* From https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_week:
*
* > Returns the week number for date. The two-argument form of WEEK()
* > enables you to specify whether the week starts on Sunday or Monday
* > and whether the return value should be in the range from 0 to 53
* > or from 1 to 53. If the mode argument is omitted, the value of the
* > default_week_format system variable is used.
* >
* > The following table describes how the mode argument works:
* >
* > Mode First day of week Range Week 1 is the first week …
* > 0 Sunday 0-53 with a Sunday in this year
* > 1 Monday 0-53 with 4 or more days this year
* > 2 Sunday 1-53 with a Sunday in this year
* > 3 Monday 1-53 with 4 or more days this year
* > 4 Sunday 0-53 with 4 or more days this year
* > 5 Monday 0-53 with a Monday in this year
* > 6 Sunday 1-53 with 4 or more days this year
* > 7 Monday 1-53 with a Monday in this year
*
* @param string $field Representing the date.
* @param int $mode The mode argument.
*/
public function week( $field, $mode ) {
/*
* From https://www.php.net/manual/en/datetime.format.php:
*
* W - ISO-8601 week number of year, weeks starting on Monday.
* Example: 42 (the 42nd week in the year)
*
* Week 1 is the first week with a Thursday in it.
*/
return intval( gmdate( 'W', strtotime( $field ) ) );
}
/**
* Simulates WEEKDAY() function in MySQL.
*
* Returns the day of the week as an integer.
* The days of the week are numbered 0 to 6:
* * 0 for Monday
* * 1 for Tuesday
* * 2 for Wednesday
* * 3 for Thursday
* * 4 for Friday
* * 5 for Saturday
* * 6 for Sunday
*
* @param string $field Representing the date.
*
* @return int
*/
public function weekday( $field ) {
/*
* date('N') returns 1 (for Monday) through 7 (for Sunday)
* That's one more than MySQL.
* Let's subtract one to make it compatible.
*/
return intval( gmdate( 'N', strtotime( $field ) ) ) - 1;
}
/**
* Method to emulate MySQL DAYOFMONTH() function.
*
* @see https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_dayofmonth
*
* @param string $field Representing the date.
*
* @return int Returns the day of the month for date as a number in the range 1 to 31.
*/
public function dayofmonth( $field ) {
return intval( gmdate( 'j', strtotime( $field ) ) );
}
/**
* Method to emulate MySQL DAYOFWEEK() function.
*
* > Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday).
* > These index values correspond to the ODBC standard. Returns NULL if date is NULL.
*
* @param string $field Representing the date.
*
* @return int Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday).
*/
public function dayofweek( $field ) {
/**
* From https://www.php.net/manual/en/datetime.format.php:
*
* `w` Numeric representation of the day of the week
* 0 (for Sunday) through 6 (for Saturday)
*/
return intval( gmdate( 'w', strtotime( $field ) ) ) + 1;
}
/**
* Method to emulate MySQL DATE() function.
*
* @see https://www.php.net/manual/en/datetime.format.php
*
* @param string $date formatted as unix time.
*
* @return string formatted as '0000-00-00'.
*/
public function date( $date ) {
return gmdate( 'Y-m-d', strtotime( $date ) );
}
/**
* Method to emulate MySQL ISNULL() function.
*
* This function returns true if the argument is null, and true if not.
*
* @param mixed $field The field to be tested.
*
* @return boolean
*/
public function isnull( $field ) {
return is_null( $field );
}
/**
* Method to emulate MySQL IF() function.
*
* As 'IF' is a reserved word for PHP, function name must be changed.
*
* @param mixed $expression The statement to be evaluated as true or false.
* @param mixed $truthy Statement or value returned if $expression is true.
* @param mixed $falsy Statement or value returned if $expression is false.
*
* @return mixed
*/
public function _if( $expression, $truthy, $falsy ) {
return ( true === $expression ) ? $truthy : $falsy;
}
/**
* Method to emulate MySQL REGEXP() function.
*
* @param string $pattern Regular expression to match.
* @param string $field Haystack.
*
* @return integer 1 if matched, 0 if not matched.
*/
public function regexp( $pattern, $field ) {
/*
* If the original query says REGEXP BINARY
* the comparison is byte-by-byte and letter casing now
* matters since lower- and upper-case letters have different
* byte codes.
*
* The REGEXP function can't be easily made to accept two
* parameters, so we'll have to use a hack to get around this.
*
* If the first character of the pattern is a null byte, we'll
* remove it and make the comparison case-sensitive. This should
* be reasonably safe since PHP does not allow null bytes in
* regular expressions anyway.
*/
if ( "\x00" === $pattern[0] ) {
$pattern = substr( $pattern, 1 );
$flags = '';
} else {
// Otherwise, the search is case-insensitive.
$flags = 'i';
}
$pattern = str_replace( '/', '\/', $pattern );
$pattern = '/' . $pattern . '/' . $flags;
return preg_match( $pattern, $field );
}
/**
* Method to emulate MySQL FIELD() function.
*
* This function gets the list argument and compares the first item to all the others.
* If the same value is found, it returns the position of that value. If not, it
* returns 0.
*
* @return int
*/
public function field() {
$num_args = func_num_args();
if ( $num_args < 2 || is_null( func_get_arg( 0 ) ) ) {
return 0;
}
$arg_list = func_get_args();
$search_string = strtolower( array_shift( $arg_list ) );
for ( $i = 0; $i < $num_args - 1; $i++ ) {
if ( strtolower( $arg_list[ $i ] ) === $search_string ) {
return $i + 1;
}
}
return 0;
}
/**
* Method to emulate MySQL LOG() function.
*
* Used with one argument, it returns the natural logarithm of X.
* <code>
* LOG(X)
* </code>
* Used with two arguments, it returns the natural logarithm of X base B.
* <code>
* LOG(B, X)
* </code>
* In this case, it returns the value of log(X) / log(B).
*
* Used without an argument, it returns false. This returned value will be
* rewritten to 0, because SQLite doesn't understand true/false value.
*
* @return double|null
*/
public function log() {
$num_args = func_num_args();
if ( 1 === $num_args ) {
$arg1 = func_get_arg( 0 );
return log( $arg1 );
}
if ( 2 === $num_args ) {
$arg1 = func_get_arg( 0 );
$arg2 = func_get_arg( 1 );
return log( $arg1 ) / log( $arg2 );
}
return null;
}
/**
* Method to emulate MySQL LEAST() function.
*
* This function rewrites the function name to SQLite compatible function name.
*
* @return mixed
*/
public function least() {
$arg_list = func_get_args();
return min( $arg_list );
}
/**
* Method to emulate MySQL GREATEST() function.
*
* This function rewrites the function name to SQLite compatible function name.
*
* @return mixed
*/
public function greatest() {
$arg_list = func_get_args();
return max( $arg_list );
}
/**
* Method to dummy out MySQL GET_LOCK() function.
*
* This function is meaningless in SQLite, so we do nothing.
*
* @param string $name Not used.
* @param integer $timeout Not used.
*
* @return string
*/
public function get_lock( $name, $timeout ) {
return '1=1';
}
/**
* Method to dummy out MySQL RELEASE_LOCK() function.
*
* This function is meaningless in SQLite, so we do nothing.
*
* @param string $name Not used.
*
* @return string
*/
public function release_lock( $name ) {
return '1=1';
}
/**
* Method to emulate MySQL UCASE() function.
*
* This is MySQL alias for upper() function. This function rewrites it
* to SQLite compatible name upper().
*
* @param string $content String to be converted to uppercase.
*
* @return string SQLite compatible function name.
*/
public function ucase( $content ) {
return "upper($content)";
}
/**
* Method to emulate MySQL LCASE() function.
*
* This is MySQL alias for lower() function. This function rewrites it
* to SQLite compatible name lower().
*
* @param string $content String to be converted to lowercase.
*
* @return string SQLite compatible function name.
*/
public function lcase( $content ) {
return "lower($content)";
}
/**
* Method to emulate MySQL UNHEX() function.
*
* For a string argument str, UNHEX(str) interprets each pair of characters
* in the argument as a hexadecimal number and converts it to the byte represented
* by the number. The return value is a binary string.
*
* @param string $number Number to be unhexed.
*
* @return string Binary string
*/
public function unhex( $number ) {
return pack( 'H*', $number );
}
/**
* Method to emulate MySQL FROM_BASE64() function.
*
* Takes a base64-encoded string and returns the decoded result as a binary
* string. Returns NULL if the argument is NULL or is not a valid base64 string.
*
* @param string|null $str The base64-encoded string.
*
* @return string|null Decoded binary string, or NULL.
*/
public function from_base64( $str ) {
if ( null === $str ) {
return null;
}
$decoded = base64_decode( $str, true );
if ( false === $decoded ) {
return null;
}
return $decoded;
}
/**
* Method to emulate MySQL TO_BASE64() function.
*
* Takes a string and returns a base64-encoded result.
* Returns NULL if the argument is NULL.
*
* @param string|null $str The string to encode.
*
* @return string|null Base64-encoded string, or NULL.
*/
public function to_base64( $str ) {
if ( null === $str ) {
return null;
}
return base64_encode( $str );
}
/**
* Method to emulate MySQL INET_NTOA() function.
*
* This function gets 4 or 8 bytes integer and turn it into the network address.
*
* @param integer $num Long integer.
*
* @return string
*/
public function inet_ntoa( $num ) {
return long2ip( $num );
}
/**
* Method to emulate MySQL INET_ATON() function.
*
* This function gets the network address and turns it into integer.
*
* @param string $addr Network address.
*
* @return int long integer
*/
public function inet_aton( $addr ) {
return absint( ip2long( $addr ) );
}
/**
* Method to emulate MySQL DATEDIFF() function.
*
* This function compares two dates value and returns the difference.
*
* @param string $start Start date.
* @param string $end End date.
*
* @return string
*/
public function datediff( $start, $end ) {
$start_date = new DateTime( $start );
$end_date = new DateTime( $end );
$interval = $end_date->diff( $start_date, false );
return $interval->format( '%r%a' );
}
/**
* Method to emulate MySQL LOCATE() function.
*
* This function returns the position if $substr is found in $str. If not,
* it returns 0. If mbstring extension is loaded, mb_strpos() function is
* used.
*
* @param string $substr Needle.
* @param string $str Haystack.
* @param integer $pos Position.
*
* @return integer
*/
public function locate( $substr, $str, $pos = 0 ) {
if ( ! extension_loaded( 'mbstring' ) ) {
$val = strpos( $str, $substr, $pos );
if ( false !== $val ) {
return $val + 1;
}
return 0;
}
$val = mb_strpos( $str, $substr, $pos );
if ( false !== $val ) {
return $val + 1;
}
return 0;
}
/**
* Method to return GMT date in the string format.
*
* @return string formatted GMT date 'dddd-mm-dd'
*/
public function utc_date() {
return gmdate( 'Y-m-d', time() );
}
/**
* Method to return GMT time in the string format.
*
* @return string formatted GMT time '00:00:00'
*/
public function utc_time() {
return gmdate( 'H:i:s', time() );
}
/**
* Method to return GMT time stamp in the string format.
*
* @return string formatted GMT timestamp 'yyyy-mm-dd 00:00:00'
*/
public function utc_timestamp() {
return gmdate( 'Y-m-d H:i:s', time() );
}
/**
* Method to return MySQL version.
*
* This function only returns the current newest version number of MySQL,
* because it is meaningless for SQLite database.
*
* @return string representing the version number: major_version.minor_version
*/
public function version() {
return '5.5';
}
/**
* A helper to covert LIKE pattern to a GLOB pattern for "LIKE BINARY" support.
* @TODO: Some of the MySQL string specifics described below are likely to
* affect also other patterns than just "LIKE BINARY". We should
* consider applying some of the conversions more broadly.
*
* @param string $pattern
* @return string
*/
public function _helper_like_to_glob_pattern( $pattern ) {
if ( null === $pattern ) {
return null;
}
/*
* 1. Escape characters that have special meaning in GLOB patterns.
*
* We need to:
* 1. Escape "]" as "[]]" to avoid interpreting "[...]" as a character class.
* 2. Escape "*" as "[*]" (must be after 1 to avoid being escaped).
* 3. Escape "?" as "[?]" (must be after 1 to avoid being escaped).
*/
$pattern = str_replace( ']', '[]]', $pattern );
$pattern = str_replace( '*', '[*]', $pattern );
$pattern = str_replace( '?', '[?]', $pattern );
/*
* 2. Convert LIKE wildcards to GLOB wildcards ("%" -> "*", "_" -> "?").
*
* We need to convert them only when they don't follow any backslashes,
* or when they follow an even number of backslashes (as "\\" is "\").
*/
$pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)%/', '$1*', $pattern );
$pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)_/', '$1?', $pattern );
/*
* 3. Unescape LIKE escape sequences.
*
* While in MySQL LIKE patterns, a backslash is usually used to escape
* special characters ("%", "_", and "\"), it works with all characters.
*
* That is:
* SELECT '\\x' prints '\x', but LIKE '\\x' is equivalent to LIKE 'x'.
*
* This is true also for multi-byte characters:
* SELECT '\\©' prints '\©', but LIKE '\\©' is equivalent to LIKE '©'.
*
* However, the multi-byte behavior is likely to depend on the charset.
* For now, we'll assume UTF-8 and thus the "u" modifier for the regex.
*/
$pattern = preg_replace( '/\\\\(.)/u', '$1', $pattern );
return $pattern;
}
}
@@ -0,0 +1,8 @@
<?php
/**
* The version of the SQLite driver.
*
* This constant needs to be updated on plugin release!
*/
define( 'SQLITE_DRIVER_VERSION', '3.0.0-rc.4' );
@@ -0,0 +1,127 @@
<?php
class WP_SQLite_Crosscheck_DB_ extends WP_SQLite_DB {
public function __construct( string $dbname ) {
parent::__construct( $dbname );
$GLOBALS['sqlite'] = $this;
$GLOBALS['mysql'] = new wpdb(
DB_USER,
DB_PASSWORD,
DB_NAME,
DB_HOST
);
// $this->resetDatabases();
}
private function resetDatabases() {
if ( file_exists( FQDB ) ) {
unlink( FQDB );
}
$GLOBALS['mysql']->query( 'DROP DATABASE IF EXISTS ' . DB_NAME );
$GLOBALS['mysql']->query( 'CREATE DATABASE ' . DB_NAME );
$GLOBALS['mysql']->query( 'USE ' . DB_NAME );
}
public function query( $query ) {
/**
* In MySQL, AUTO_INCREMENT columns don't reuse IDs assigned in rollback transactions
* In SQLite, AUTOINCREMENT columns do reuse IDs assigned in rollback transactions
*
* Let's store the current AUTOINCREMENT value for each table, and restore it afterwards.
*/
if ( preg_match( '/^\s*rollback/i', $query ) ) {
$autoincrements = array();
$tables = $GLOBALS['@pdo']->query( "SELECT name as `table` FROM sqlite_master WHERE type='table' ORDER BY name" )->fetchAll();
foreach ( $tables as $table ) {
$table = $table['table'];
$autoincrement = $GLOBALS['@pdo']->query( "SELECT seq FROM sqlite_sequence WHERE name = '$table'" )->fetchColumn();
$autoincrements[ $table ] = $autoincrement ?: 1;
}
}
$sqlite_retval = parent::query( $query );
if ( preg_match( '/^\s*rollback/i', $query ) ) {
foreach ( $autoincrements as $table => $autoincrement ) {
$GLOBALS['@pdo']->query( "UPDATE sqlite_sequence SET seq = $autoincrement WHERE name = '$table'" );
}
}
$this->crosscheck( $query, $sqlite_retval );
return $sqlite_retval;
}
private function crosscheck( $query, $sqlite_retval ) {
// echo $query."\n\n";
// Be lenient on cross-checking some query types
if ( preg_match( '/^\s*SET storage_engine/i', $query ) ) {
return;
}
$this->show_errors = false;
$this->suppress_errors = true;
$GLOBALS['mysql']->show_errors = false;
$GLOBALS['mysql']->suppress_errors = true;
ob_start();
$mysql_retval = $GLOBALS['mysql']->query( $query );
ob_end_clean();
$tests = array(
array( 'retval', $mysql_retval, $sqlite_retval ),
array( 'num_rows', $GLOBALS['mysql']->num_rows, $GLOBALS['sqlite']->num_rows ),
array( 'insert_id', $GLOBALS['mysql']->insert_id, $GLOBALS['sqlite']->insert_id ),
array( 'rows_affected', $GLOBALS['mysql']->rows_affected, $GLOBALS['sqlite']->rows_affected ),
);
foreach ( $tests as $test ) {
list($factor, $mysql, $sqlite) = $test;
if ( $mysql !== $sqlite ) {
if ( 'insert_id' === $factor ) {
// On multi-inserts MySQL returns the first inserted ID
// while SQLite returns the last one. The cached insert_id
// value stays the same for a number of subsequent queries.
// Let's forgive this for now.
continue;
}
if ( 'rows_affected' === $factor && $mysql_retval === $mysql ) {
// SQLite doesn't provide the rowcount() functionality
continue;
}
if ( 'retval' === $factor && $GLOBALS['mysql']->rows_affected === $mysql ) {
// SQLite doesn't provide the rowcount() functionality
continue;
}
echo "======================================================\n";
echo "======== *** $factor *** differed for query ========= \n";
echo "======================================================\n";
echo "MySQL query: \n";
echo "$query\n\n";
echo "SQLite queries: \n";
foreach ( $this->dbh->last_translation->queries as $query ) {
echo $query->sql . "\n";
}
echo "\n";
$this->report_factor(
'error',
$GLOBALS['mysql']->last_error,
$GLOBALS['sqlite']->last_error
);
foreach ( $tests as $test ) {
$this->report_factor(
$test[0],
$test[1],
$test[2]
);
}
// throw new Exception();
break;
}
}
}
private function report_factor( $factor, $mysql, $sqlite ) {
echo "$factor: \n";
echo ' MySQL: ' . var_export( $mysql, true ) . "\n";
echo ' SQLite: ' . var_export( $sqlite, true ) . "\n\n";
}
}
@@ -0,0 +1,675 @@
<?php
/**
* Extend and replace the wpdb class.
*
* @package wp-sqlite-integration
* @since 1.0.0
*/
/**
* This class extends wpdb and replaces it.
*
* It also rewrites some methods that use mysql specific functions.
*/
class WP_SQLite_DB extends wpdb {
/**
* Database Handle
*
* @var WP_SQLite_Driver
*/
protected $dbh;
/**
* Backward compatibility, see wpdb::$allow_unsafe_unquoted_parameters.
*
* This property is mirroring "wpdb::$allow_unsafe_unquoted_parameters",
* because some tests are accessing it externally using PHP reflection.
*
* @var
*/
private $allow_unsafe_unquoted_parameters = true;
/**
* Connects to the SQLite database.
*
* Unlike for MySQL, no credentials and host are needed.
*
* @param string $dbname Database name.
*/
public function __construct( $dbname ) {
/**
* We need to initialize the "$wpdb" global early, so that the SQLite
* driver can configure the database. The call stack goes like this:
*
* 1. The "parent::__construct()" call executes "$this->db_connect()".
* 2. The database connection call initializes the SQLite driver.
* 3. The SQLite driver initializes and runs "WP_SQLite_Configurator".
* 4. The configurator uses "WP_SQLite_Information_Schema_Reconstructor",
* which requires "wp-admin/includes/schema.php" when in WordPress.
* 5. The "wp-admin/includes/schema.php" requires the "$wpdb" global,
* which creates a circular dependency.
*/
$GLOBALS['wpdb'] = $this;
parent::__construct( '', '', $dbname, '' );
$this->charset = 'utf8mb4';
}
/**
* Method to set character set for the database.
*
* This overrides wpdb::set_charset(), only to dummy out the MySQL function.
*
* @see wpdb::set_charset()
*
* @param resource $dbh The resource given by mysql_connect.
* @param string $charset Optional. The character set. Default null.
* @param string $collate Optional. The collation. Default null.
*/
public function set_charset( $dbh, $charset = null, $collate = null ) {
}
/**
* Method to get the character set for the database.
* Hardcoded to utf8mb4 for now.
*
* @param string $table The table name.
* @param string $column The column name.
*
* @return string The character set.
*/
public function get_col_charset( $table, $column ) {
// Hardcoded for now.
return 'utf8mb4';
}
/**
* Changes the current SQL mode, and ensures its WordPress compatibility.
*
* If no modes are passed, it will ensure the current MySQL server modes are compatible.
*
* This overrides wpdb::set_sql_mode() while closely mirroring its implementation.
*
* @param array $modes Optional. A list of SQL modes to set. Default empty array.
*/
public function set_sql_mode( $modes = array() ) {
if ( empty( $modes ) ) {
$result = $this->dbh->query( 'SELECT @@SESSION.sql_mode' );
if ( ! isset( $result[0] ) ) {
return;
}
$modes_str = $result[0]->{'@@SESSION.sql_mode'};
if ( empty( $modes_str ) ) {
return;
}
$modes = explode( ',', $modes_str );
}
$modes = array_change_key_case( $modes, CASE_UPPER );
/**
* Filters the list of incompatible SQL modes to exclude.
*
* @since 3.9.0
*
* @param array $incompatible_modes An array of incompatible modes.
*/
$incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );
foreach ( $modes as $i => $mode ) {
if ( in_array( $mode, $incompatible_modes, true ) ) {
unset( $modes[ $i ] );
}
}
$modes_str = implode( ',', $modes );
$this->dbh->query( "SET SESSION sql_mode='$modes_str'" );
}
/**
* Closes the current database connection.
* Noop in SQLite.
*
* @return bool True to indicate the connection was successfully closed.
*/
public function close() {
return true;
}
/**
* Method to select the database connection.
*
* This overrides wpdb::select(), only to dummy out the MySQL function.
*
* @see wpdb::select()
*
* @param string $db MySQL database name. Not used.
* @param resource|null $dbh Optional link identifier.
*/
public function select( $db, $dbh = null ) {
$this->ready = true;
}
/**
* Method to escape characters.
*
* This overrides wpdb::_real_escape() to avoid using mysql_real_escape_string().
*
* @see wpdb::_real_escape()
*
* @param string $data The string to escape.
*
* @return string escaped
*/
public function _real_escape( $data ) {
if ( ! is_scalar( $data ) ) {
return '';
}
$escaped = addslashes( $data );
return $this->add_placeholder_escape( $escaped );
}
/**
* Prints SQL/DB error.
*
* This overrides wpdb::print_error() while closely mirroring its implementation.
*
* @global array $EZSQL_ERROR Stores error information of query and error string.
*
* @param string $str The error to display.
* @return void|false Void if the showing of errors is enabled, false if disabled.
*/
public function print_error( $str = '' ) {
global $EZSQL_ERROR;
if ( ! $str ) {
$str = $this->last_error;
}
$EZSQL_ERROR[] = array(
'query' => $this->last_query,
'error_str' => $str,
);
if ( $this->suppress_errors ) {
return false;
}
$caller = $this->get_caller();
if ( $caller ) {
// Not translated, as this will only appear in the error log.
$error_str = sprintf( 'WordPress database error %1$s for query %2$s made by %3$s', $str, $this->last_query, $caller );
} else {
$error_str = sprintf( 'WordPress database error %1$s for query %2$s', $str, $this->last_query );
}
error_log( $error_str );
// Are we showing errors?
if ( ! $this->show_errors ) {
return false;
}
wp_load_translations_early();
// If there is an error then take note of it.
if ( is_multisite() ) {
$msg = sprintf(
"%s [%s]\n%s\n",
__( 'WordPress database error:' ),
$str,
$this->last_query
);
if ( defined( 'ERRORLOGFILE' ) ) {
error_log( $msg, 3, ERRORLOGFILE );
}
if ( defined( 'DIEONDBERROR' ) ) {
wp_die( $msg );
}
} else {
$str = htmlspecialchars( $str, ENT_QUOTES );
$query = htmlspecialchars( $this->last_query, ENT_QUOTES );
printf(
'<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>',
__( 'WordPress database error:' ),
$str,
$query
);
}
}
/**
* Method to flush cached data.
*
* This overrides wpdb::flush(). This is not necessarily overridden, because
* $result will never be resource.
*
* @see wpdb::flush
*/
public function flush() {
$this->last_result = array();
$this->col_info = null;
$this->last_query = null;
$this->rows_affected = 0;
$this->num_rows = 0;
$this->last_error = '';
$this->result = null;
}
/**
* Method to do the database connection.
*
* This overrides wpdb::db_connect() to avoid using MySQL function.
*
* @see wpdb::db_connect()
*
* @param bool $allow_bail Not used.
* @return void
*/
public function db_connect( $allow_bail = true ) {
if ( $this->dbh ) {
return;
}
$this->init_charset();
$pdo = null;
if ( isset( $GLOBALS['@pdo'] ) ) {
$pdo = $GLOBALS['@pdo'];
}
// Migrate the database file from a legacy path, if it exists.
if ( ! defined( 'DB_FILE' ) && ! file_exists( FQDB ) ) {
$old_db_path = FQDBDIR . '.ht.sqlite.php';
if ( file_exists( $old_db_path ) ) {
if ( ! rename( $old_db_path, FQDB ) ) {
wp_die( 'Failed to rename database file.', 'Error!' );
}
foreach ( array( '-wal', '-shm', '-journal' ) as $suffix ) {
if ( file_exists( $old_db_path . $suffix ) ) {
if ( ! rename( $old_db_path . $suffix, FQDB . $suffix ) ) {
wp_die( 'Failed to rename database file.', 'Error!' );
}
}
}
}
}
if ( null === $this->dbname || '' === $this->dbname ) {
$this->bail(
'The database name was not set. The SQLite driver requires a database name to be set to emulate MySQL information schema tables.',
'db_connect_fail'
);
return false;
}
$this->ensure_database_directory( FQDB );
try {
$connection = new WP_SQLite_Connection(
array(
'pdo' => $pdo,
'path' => FQDB,
'journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null,
)
);
$this->dbh = new WP_SQLite_Driver( $connection, $this->dbname );
$GLOBALS['@pdo'] = $this->dbh->get_connection()->get_pdo();
} catch ( Throwable $e ) {
$this->last_error = $this->format_error_message( $e );
}
if ( $this->last_error ) {
return false;
}
$this->ready = true;
$this->set_sql_mode();
}
/**
* Method to dummy out wpdb::check_connection()
*
* @param bool $allow_bail Not used.
*
* @return bool
*/
public function check_connection( $allow_bail = true ) {
return true;
}
/**
* Prepares a SQL query for safe execution.
*
* See "wpdb::prepare()". This override only fixes a WPDB test issue.
*
* @param string $query Query statement with `sprintf()`-like placeholders.
* @param array|mixed $args The array of variables or the first variable to substitute.
* @param mixed ...$args Further variables to substitute when using individual arguments.
* @return string|void Sanitized query string, if there is a query to prepare.
*/
public function prepare( $query, ...$args ) {
/*
* Sync "$allow_unsafe_unquoted_parameters" with the WPDB parent property.
* This is only needed because some WPDB tests are accessing the private
* property externally via PHP reflection. This should be fixed WP tests.
*/
$wpdb_allow_unsafe_unquoted_parameters = $this->__get( 'allow_unsafe_unquoted_parameters' );
if ( $wpdb_allow_unsafe_unquoted_parameters !== $this->allow_unsafe_unquoted_parameters ) {
$property = new ReflectionProperty( 'wpdb', 'allow_unsafe_unquoted_parameters' );
$property->setAccessible( true );
$property->setValue( $this, $this->allow_unsafe_unquoted_parameters );
$property->setAccessible( false );
}
return parent::prepare( $query, ...$args );
}
/**
* Performs a database query.
*
* This overrides wpdb::query() while closely mirroring its implementation.
*
* @see wpdb::query()
*
* @param string $query Database query.
*
* @param string $query Database query.
* @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
* affected/selected for all other queries. Boolean false on error.
*/
public function query( $query ) {
// Query Monitor integration:
$query_monitor_active = defined( 'SQLITE_QUERY_MONITOR_LOADED' ) && SQLITE_QUERY_MONITOR_LOADED;
if ( $query_monitor_active && $this->show_errors ) {
$this->hide_errors();
}
if ( ! $this->ready ) {
return false;
}
$query = apply_filters( 'query', $query );
if ( ! $query ) {
$this->insert_id = 0;
return false;
}
$this->flush();
// Log how the function was called.
$this->func_call = "\$db->query(\"$query\")";
// Keep track of the last query for debug.
$this->last_query = $query;
// Save the query count before running another query.
$last_query_count = count( $this->queries ?? array() );
/*
* @TODO: WPDB uses "$this->check_current_query" to check table/column
* charset and strip all invalid characters from the query.
* This is an involved process that we can bypass for SQLite,
* if we simply strip all invalid UTF-8 characters from the query.
*
* To do so, mb_convert_encoding can be used with an optional
* fallback to a htmlspecialchars method. E.g.:
* https://github.com/nette/utils/blob/be534713c227aeef57ce1883fc17bc9f9e29eca2/src/Utils/Strings.php#L42
*/
$this->_do_query( $query );
if ( $this->last_error ) {
// Clear insert_id on a subsequent failed insert.
if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
$this->insert_id = 0;
}
$this->print_error();
return false;
}
if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
$return_val = true;
} elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
$this->rows_affected = $this->dbh->get_last_return_value();
// Take note of the insert_id.
if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
$this->insert_id = $this->dbh->get_insert_id();
}
// Return number of rows affected.
$return_val = $this->rows_affected;
} else {
$num_rows = 0;
if ( is_array( $this->result ) ) {
$this->last_result = $this->result;
$num_rows = count( $this->result );
}
// Log and return the number of rows selected.
$this->num_rows = $num_rows;
$return_val = $num_rows;
}
// Query monitor integration:
if ( $query_monitor_active && class_exists( 'QM_Backtrace' ) ) {
if ( did_action( 'qm/cease' ) ) {
$this->queries = array();
}
$i = $last_query_count;
if ( ! isset( $this->queries[ $i ] ) ) {
return $return_val;
}
$this->queries[ $i ]['trace'] = new QM_Backtrace();
if ( ! isset( $this->queries[ $i ][3] ) ) {
$this->queries[ $i ][3] = $this->time_start;
}
if ( $this->last_error && ! $this->suppress_errors ) {
$this->queries[ $i ]['result'] = new WP_Error( 'qmdb', $this->last_error );
} else {
$this->queries[ $i ]['result'] = (int) $return_val;
}
// Add SQLite query data.
$this->queries[ $i ]['sqlite_queries'] = $this->dbh->get_last_sqlite_queries();
}
return $return_val;
}
/**
* Internal function to perform the SQLite query call.
*
* This closely mirrors wpdb::_do_query().
*
* @see wpdb::_do_query()
*
* @param string $query The query to run.
*/
private function _do_query( $query ) {
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
$this->timer_start();
}
try {
$this->result = $this->dbh->query( $query );
} catch ( Throwable $e ) {
$this->last_error = $this->format_error_message( $e );
}
++$this->num_queries;
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
$this->log_query(
$query,
$this->timer_stop(),
$this->get_caller(),
$this->time_start,
array()
);
}
}
/**
* Method to set the class variable $col_info.
*
* This overrides wpdb::load_col_info(), which uses a mysql function.
*
* @see wpdb::load_col_info()
*/
protected function load_col_info() {
if ( $this->col_info ) {
return;
}
$this->col_info = array();
foreach ( $this->dbh->get_last_column_meta() as $column ) {
$this->col_info[] = (object) array(
'name' => $column['name'],
'orgname' => $column['mysqli:orgname'],
'table' => $column['table'],
'orgtable' => $column['mysqli:orgtable'],
'def' => '', // Unused, always ''.
'db' => $column['mysqli:db'],
'catalog' => 'def', // Unused, always 'def'.
'max_length' => 0, // As of PHP 8.1, this is always 0.
'length' => $column['len'],
'charsetnr' => $column['mysqli:charsetnr'],
'flags' => $column['mysqli:flags'],
'type' => $column['mysqli:type'],
'decimals' => $column['precision'],
);
}
}
/**
* Method to return what the database can do.
*
* This overrides wpdb::has_cap() to avoid using MySQL functions.
* SQLite supports subqueries, but not support collation, group_concat and set_charset.
*
* @see wpdb::has_cap()
*
* @param string $db_cap The feature to check for. Accepts 'collation',
* 'group_concat', 'subqueries', 'set_charset',
* 'utf8mb4', or 'utf8mb4_520'.
*
* @return bool Whether the database feature is supported, false otherwise.
*/
public function has_cap( $db_cap ) {
return 'subqueries' === strtolower( $db_cap );
}
/**
* Method to return database version number.
*
* This overrides wpdb::db_version() to avoid using MySQL function.
* It returns mysql version number, but it means nothing for SQLite.
* So it return the newest mysql version.
*
* @see wpdb::db_version()
*/
public function db_version() {
return '8.0';
}
/**
* Returns the version of the SQLite engine.
*
* @return string SQLite engine version as a string.
*/
public function db_server_info() {
return $this->dbh->get_sqlite_version();
}
/**
* Make sure the SQLite database directory exists and is writable.
* Create .htaccess and index.php files to prevent direct access.
*
* @param string $database_path The path to the SQLite database file.
*/
private function ensure_database_directory( string $database_path ) {
$dir = dirname( $database_path );
// Set the umask to 0000 to apply permissions exactly as specified.
// A non-zero umask affects new file and directory permissions.
$umask = umask( 0 );
// Ensure database directory.
if ( ! is_dir( $dir ) ) {
if ( ! @mkdir( $dir, 0700, true ) ) {
wp_die( sprintf( 'Failed to create database directory: %s', $dir ), 'Error!' );
}
}
if ( ! is_writable( $dir ) ) {
wp_die( sprintf( 'Database directory is not writable: %s', $dir ), 'Error!' );
}
// Ensure .htaccess file to prevent direct access.
$path = $dir . DIRECTORY_SEPARATOR . '.htaccess';
if ( ! is_file( $path ) ) {
$result = file_put_contents( $path, 'DENY FROM ALL', LOCK_EX );
if ( false === $result ) {
wp_die( sprintf( 'Failed to create file: %s', $path ), 'Error!' );
}
chmod( $path, 0600 );
}
// Ensure index.php file to prevent direct access.
$path = $dir . DIRECTORY_SEPARATOR . 'index.php';
if ( ! is_file( $path ) ) {
$result = file_put_contents( $path, '<?php // Silence is gold. ?>', LOCK_EX );
if ( false === $result ) {
wp_die( sprintf( 'Failed to create file: %s', $path ), 'Error!' );
}
chmod( $path, 0600 );
}
// Restore the original umask value.
umask( $umask );
}
/**
* Format SQLite driver error message.
*
* @return string
*/
private function format_error_message( Throwable $e ) {
$output = '<div style="clear:both">&nbsp;</div>' . PHP_EOL;
// Queries.
if ( $e instanceof WP_SQLite_Driver_Exception ) {
$driver = $e->getDriver();
$output .= '<div class="queries" style="clear:both;margin-bottom:2px;border:red dotted thin;">' . PHP_EOL;
$output .= '<p>MySQL query:</p>' . PHP_EOL;
$output .= '<p>' . $driver->get_last_mysql_query() . '</p>' . PHP_EOL;
$output .= '<p>Queries made or created this session were:</p>' . PHP_EOL;
$output .= '<ol>' . PHP_EOL;
foreach ( $driver->get_last_sqlite_queries() as $q ) {
$message = "Executing: {$q['sql']} | " . ( $q['params'] ? 'parameters: ' . implode( ', ', $q['params'] ) : '(no parameters)' );
$output .= '<li>' . htmlspecialchars( $message ) . '</li>' . PHP_EOL;
}
$output .= '</ol>' . PHP_EOL;
$output .= '</div>' . PHP_EOL;
}
// Message.
$output .= '<div style="clear:both;margin-bottom:2px;border:red dotted thin;" class="error_message" style="border-bottom:dotted blue thin;">' . PHP_EOL;
$output .= $e->getMessage() . PHP_EOL;
$output .= '</div>' . PHP_EOL;
// Backtrace.
$output .= '<p>Backtrace:</p>' . PHP_EOL;
$output .= '<pre>' . $e->getTraceAsString() . '</pre>' . PHP_EOL;
return $output;
}
}
@@ -0,0 +1,71 @@
<?php
/**
* Main integration file.
*
* @package wp-sqlite-integration
* @since 1.0.0
*/
/**
* Load the "SQLITE_DRIVER_VERSION" constant.
*/
require_once __DIR__ . '/../database/version.php';
// Require the constants file.
require_once __DIR__ . '/../../constants.php';
// Bail early if DB_ENGINE is not defined as sqlite.
if ( ! defined( 'DB_ENGINE' ) || 'sqlite' !== DB_ENGINE ) {
return;
}
if ( ! extension_loaded( 'pdo' ) ) {
wp_die(
new WP_Error(
'pdo_not_loaded',
sprintf(
'<h1>%1$s</h1><p>%2$s</p>',
'PHP PDO Extension is not loaded',
'Your PHP installation appears to be missing the PDO extension which is required for this version of WordPress and the type of database you have specified.'
)
),
'PHP PDO Extension is not loaded.'
);
}
if ( ! extension_loaded( 'pdo_sqlite' ) ) {
wp_die(
new WP_Error(
'pdo_driver_not_loaded',
sprintf(
'<h1>%1$s</h1><p>%2$s</p>',
'PDO Driver for SQLite is missing',
'Your PHP installation appears not to have the right PDO drivers loaded. These are required for this version of WordPress and the type of database you have specified.'
)
),
'PDO Driver for SQLite is missing.'
);
}
require_once __DIR__ . '/../database/load.php';
require_once __DIR__ . '/class-wp-sqlite-db.php';
require_once __DIR__ . '/install-functions.php';
$db_name = defined( 'DB_NAME' ) ? DB_NAME : '';
/*
* Debug: Cross-check with MySQL.
* This is for debugging purpose only and requires files
* that are present in the GitHub repository
* but not the plugin published on WordPress.org.
*/
$crosscheck_tests_file_path = __DIR__ . '/class-wp-sqlite-crosscheck-db.php';
if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK && file_exists( $crosscheck_tests_file_path ) ) {
require_once $crosscheck_tests_file_path;
$GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB( $db_name );
} else {
$GLOBALS['wpdb'] = new WP_SQLite_DB( $db_name );
// Boot the Query Monitor plugin if it is active.
require_once __DIR__ . '/../../integrations/query-monitor/boot.php';
}
@@ -0,0 +1,232 @@
<?php
/**
* Main integration file.
*
* @package wp-sqlite-integration
* @since 1.0.0
*/
/**
* Function to create tables according to the schemas of WordPress.
*
* This is executed only once while installation.
*
* @since 1.0.0
*
* @return boolean
*
* @throws PDOException If the database connection fails.
*/
function sqlite_make_db_sqlite() {
global $wpdb;
include_once ABSPATH . 'wp-admin/includes/schema.php';
$table_schemas = wp_get_db_schema();
$queries = explode( ';', $table_schemas );
try {
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class; // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
$pdo = new $pdo_class( 'sqlite:' . FQDB, null, null, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) ); // phpcs:ignore WordPress.DB.RestrictedClasses
} catch ( PDOException $err ) {
$err_data = $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$message = 'Database connection error!<br />';
$message .= sprintf( 'Error message is: %s', $err_data[2] );
wp_die( $message, 'Database Error!' );
}
$translator = new WP_SQLite_Driver(
new WP_SQLite_Connection( array( 'pdo' => $pdo ) ),
$wpdb->dbname
);
$query = null;
try {
$translator->begin_transaction();
foreach ( $queries as $query ) {
$query = trim( $query );
if ( empty( $query ) ) {
continue;
}
$translator->query( $query );
}
$translator->commit();
} catch ( PDOException $err ) {
$translator->rollback();
$message = sprintf(
'Error occurred while creating tables or indexes...<br />Query was: %s<br />',
var_export( $query, true )
);
$message .= sprintf( 'Error message is: %s', $err->getMessage() );
wp_die( $message, 'Database Error!' );
}
/*
* Debug: Cross-check with MySQL.
* This is for debugging purpose only and requires files
* that are present in the GitHub repository
* but not the plugin published on WordPress.org.
*/
if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK ) {
$host = DB_HOST;
$port = 3306;
if ( str_contains( $host, ':' ) ) {
$host_parts = explode( ':', $host );
$host = $host_parts[0];
$port = $host_parts[1];
}
$dsn = 'mysql:host=' . $host . '; port=' . $port . '; dbname=' . DB_NAME;
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\MySQL::class : PDO::class; // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
$pdo_mysql = new $pdo_class( $dsn, DB_USER, DB_PASSWORD, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) ); // phpcs:ignore WordPress.DB.RestrictedClasses.mysql__PDO
$pdo_mysql->query( 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' );
$pdo_mysql->query( 'SET time_zone = "+00:00";' );
foreach ( $queries as $query ) {
$query = trim( $query );
if ( empty( $query ) ) {
continue;
}
try {
$pdo_mysql->beginTransaction();
$pdo_mysql->query( $query );
} catch ( PDOException $err ) {
$err_data = $err->errorInfo; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$err_code = $err_data[1];
// phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
if ( 5 == $err_code || 6 == $err_code ) {
// If the database is locked, commit again.
$pdo_mysql->commit();
} else {
$pdo_mysql->rollBack();
$message = sprintf(
'Error occurred while creating tables or indexes...<br />Query was: %s<br />',
var_export( $query, true )
);
$message .= sprintf( 'Error message is: %s', $err_data[2] );
wp_die( $message, 'Database Error!' );
}
}
}
}
$pdo = null;
return true;
}
if ( ! function_exists( 'wp_install' ) ) {
/**
* Installs the site.
*
* Runs the required functions to set up and populate the database,
* including primary admin user and initial options.
*
* @since 1.0.0
*
* @param string $blog_title Site title.
* @param string $user_name User's username.
* @param string $user_email User's email.
* @param bool $is_public Whether the site is public.
* @param string $deprecated Optional. Not used.
* @param string $user_password Optional. User's chosen password. Default empty (random password).
* @param string $language Optional. Language chosen. Default empty.
* @return array {
* Data for the newly installed site.
*
* @type string $url The URL of the site.
* @type int $user_id The ID of the site owner.
* @type string $password The password of the site owner, if their user account didn't already exist.
* @type string $password_message The explanatory message regarding the password.
* }
*/
function wp_install( $blog_title, $user_name, $user_email, $is_public, $deprecated = '', $user_password = '', $language = '' ) {
if ( ! empty( $deprecated ) ) {
_deprecated_argument( __FUNCTION__, '2.6.0' );
}
wp_check_mysql_version();
wp_cache_flush();
/* SQLite changes: Replace the call to make_db_current_silent() with sqlite_make_db_sqlite(). */
sqlite_make_db_sqlite(); // phpcs:ignore PHPCompatibility.Extensions.RemovedExtensions.sqliteRemoved
populate_options();
populate_roles();
update_option( 'blogname', $blog_title );
update_option( 'admin_email', $user_email );
update_option( 'blog_public', $is_public );
// Freshness of site - in the future, this could get more specific about actions taken, perhaps.
update_option( 'fresh_site', 1 );
if ( $language ) {
update_option( 'WPLANG', $language );
}
$guessurl = wp_guess_url();
update_option( 'siteurl', $guessurl );
// If not a public site, don't ping.
if ( ! $is_public ) {
update_option( 'default_pingback_flag', 0 );
}
/*
* Create default user. If the user already exists, the user tables are
* being shared among sites. Just set the role in that case.
*/
$user_id = username_exists( $user_name );
$user_password = trim( $user_password );
$email_password = false;
$user_created = false;
if ( ! $user_id && empty( $user_password ) ) {
$user_password = wp_generate_password( 12, false );
$message = __( '<strong><em>Note that password</em></strong> carefully! It is a <em>random</em> password that was generated just for you.', 'sqlite-database-integration' );
$user_id = wp_create_user( $user_name, $user_password, $user_email );
update_user_meta( $user_id, 'default_password_nag', true );
$email_password = true;
$user_created = true;
} elseif ( ! $user_id ) {
// Password has been provided.
$message = '<em>' . __( 'Your chosen password.', 'sqlite-database-integration' ) . '</em>';
$user_id = wp_create_user( $user_name, $user_password, $user_email );
$user_created = true;
} else {
$message = __( 'User already exists. Password inherited.', 'sqlite-database-integration' );
}
$user = new WP_User( $user_id );
$user->set_role( 'administrator' );
if ( $user_created ) {
$user->user_url = $guessurl;
wp_update_user( $user );
}
wp_install_defaults( $user_id );
wp_install_maybe_enable_pretty_permalinks();
flush_rewrite_rules();
wp_new_blog_notification( $blog_title, $guessurl, $user_id, ( $email_password ? $user_password : __( 'The password you chose during installation.', 'sqlite-database-integration' ) ) );
wp_cache_flush();
/**
* Fires after a site is fully installed.
*
* @since 3.9.0
*
* @param WP_User $user The site owner.
*/
do_action( 'wp_install', $user );
return array(
'url' => $guessurl,
'user_id' => $user_id,
'password' => $user_password,
'password_message' => $message,
);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,675 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. 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
them 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 prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. 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.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey 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;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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 that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
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.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
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.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
@@ -0,0 +1,280 @@
# Admin and Site Enhancements (ASE)
Contributors: qriouslad
Donate link: https://bowo.io/asenha-sp-rdm
Tags: enhancements, tweaks, optimizations, tools
Requires at least: 4.6
Tested up to: 7.0
Stable tag: 8.8.5
Requires PHP: 5.6
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
![](.wordpress-org/banner-772x250.png)
Duplicate post, post order, image resize, email via SMTP, admin menu editor, custom css / code, disable gutenberg and much more in a single plugin.
## Description
Admin and Site Enhancements (ASE) helps you to **easily enhance** various admin workflows and site aspects while **replacing multiple plugins** doing it.
**Pro version** with Lifetime Deal (LTD) is available at [www.wpase.com](https://www.wpase.com/rdme-to-web).
Here's a quick walkthrough of ASE Free at v7.1.5 by Jakson.
https://www.youtube.com/watch?v=ZEiIKfz2p2Q
Here's a detailed walkthrough of ASE Free at v7.0.3 by Pascal Claro.
https://www.youtube.com/watch?v=bX-2gmpCEMU
[**See more video reviews >>**](https://www.wpase.com/video-reviews/)
### What Users Say
_"ASE easily **replaces a dozen or more plugins** I install on **every website project**. Super lightweight and easy to use."_ ~[NetzzJD](https://wordpress.org/support/topic/amazing-must-have-plugin-2/)
_"**I bought the lifetime deal and was blown away by the quality**, and the free version replaces SO many other plugins too. LOVE THIS!"_ ~[Jacob Wonder](https://wordpress.org/support/topic/amazing-plugin-2545/)
_"ASE is an amazing plugin! **Time and money saver**. Thank you!"_ ~[Iulian Baciu](https://wordpress.org/support/topic/amazing-plugin-precious/)
[**See more reviews >>**](https://www.wpase.com/)
### FEATURES & MODULES
**76 modules** in total:
**58 free modules** (32 has Pro features) | **18 Pro modules**
[**See all features >>**](https://www.wpase.com/features/)
### Content Management
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Custom Content Types**. Register custom post types (CPT), custom taxonomies, custom field groups and options pages that integrates with Bricks, Breakdance, Oxygen and Elementor, and compatible with various block themes and plugins. Also integrates with WPML and works with TranslatePress to create multilingual sites. An alternative for ACF Pro, Meta Box, Toolset, Pods, JetEngine or ACPT.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Post Type Switcher**. Switch the post type of one or more posts to a different post type.
* **Content Duplication**. Duplicate pages, posts and public CPTs. [ASE Pro](https://www.wpase.com/rdme-to-web) enables duplication only for certain user roles and certain public and non-public post types, and adds location option for duplication links.
* **Content Order**. Drag-and-drop custom ordering of hierarchical post types. [ASE Pro](https://www.wpase.com/rdme-to-web) enables ordering of non-hierarchical post types, including media / attachments, reordering and changing the parent of child posts is supported, as well as applying the custom order on the frontend.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Terms Order**. Drag-and-drop custom ordering of terms and child terms from various taxonomies and apply the custom order on the frontend.
* **Media Files Visibility Control**: Limit media files visibility so only administrators can see all media files. Non-administrator users will only see media files they uploaded themselves. [ASE Pro](https://www.wpase.com/rdme-to-web) adds an option to specify which non-administrator user roles will have such limitation.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Media Categories**. Hierarchical categories for the media library with drag-and-drop categorization.
* **Media Replacement**. Replace any type of media file with a new one while ensuring no existing links will break. [ASE Pro](https://www.wpase.com/rdme-to-web) allows replacing media from the grid view of media library.
* **SVG Upload**. Allow some or all user roles to upload SVG files with sanitization to keep things secure.
* **AVIF Upload**. Enable uploading AVIF files in the Media Library.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Public Preview for Drafts**. Public preview for draft and scheduled posts from some or all public post types.
* **External Permalinks**. Enable pages, posts and/or custom post types to have permalinks pointing to external URLs. [ASE Pro](https://www.wpase.com/rdme-to-web) allows enabling only on / except on certain post types, or on all post types.
* **Open All External Links in New Tab**. Force all links to external sites to open in new browser tab. [ASE Pro](https://www.wpase.com/rdme-to-web) allows excluding links with certain domains / subdomains from opening in a new tab and from getting the rel="nofollow" attribute.
* **Allow Custom Navigation Menu Items to Open in New Tab**. Allow custom navigation menu items to have links that open in a new browser tab.
* **Auto-Publish Posts with Missed Schedule**. Trigger publishing of scheduled posts marked with "missed schedule", anytime the site is visited.
### Admin Interface
* **Clean Up Admin Bar**. Remove various default elements from the admin bar. [ASE Pro](https://www.wpase.com/rdme-to-web) enables removal of admin bar items from other plugins.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Admin Bar Custom Elements**. Add custom menu and submenu items to the admin bar.
* **Hide Admin Notices**. Move notices into a dedicated panel accessible via the admin bar. [ASE Pro](https://www.wpase.com/rdme-to-web) enables completely hiding notices for non-admins.
* **Disable Dashboard Widgets**. Completely disable some or all widgets.
* **Hide Admin Bar**. Frontend hiding for all or some user roles. [ASE Pro](https://www.wpase.com/rdme-to-web) allows hiding on the backend and make frontend admin bar toggleable.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Admin Logo**. Show custom logo in the admin bar or the (top of) admin menu.
* **Wider Admin Menu**. Up to 300px wide.
* **Admin Menu Editor / Organizer**. Customize order for the admin menu. Change menu item title or hide some items. [ASE Pro](https://www.wpase.com/rdme-to-web) allows always hiding menu items for some or all user roles, adding custom menu and submenu items, reordering submenu items and always allowing eligible users to view and access a menu item.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Admin Columns Manager**. Organize columns in the admin list tables. Supports columns for ASE, ACF and Meta Box custom fields.
* **Show Custom Taxonomy Filters**. Show additional filter(s) on list tables for hierarchical, custom taxonomies. [ASE Pro](https://www.wpase.com/rdme-to-web) allows showing additional filter(s) for non-hierarchical taxonomies.
* **Enhance List Tables**. Improve the usefulness of listing pages of various post types by adding / removing columns and elements, e.g. featured image, excerpt, last modified, ID, media file size columns.
* **Various Admin UI Enhancements**:
* **Media Library Infinite Scrolling**: Re-enable this in the grid view.
* **Display Active Plugins First**: On the plugins list.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Preserve Taxonomy Hierarchy**: Preserve the visual hierarchy of taxonomy terms checklist in the classic editor.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Enable Dashboard Columns Settings**: Set columns layout between 1 to 4 columns.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Add User Role Slug(s) to Admin Body Classes**.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Add Usermame to Admin Body Classes**.
* **Custom Admin Footer Text**: Customize the text you see on the footer of wp-admin. [ASE Pro](https://www.wpase.com/rdme-to-web) enables media insertion.
### Log In/Out & Register
* **Change Login URL**. Make the login URL more memorable and secure. [ASE Pro](https://www.wpase.com/rdme-to-web) adds compatibility with login process in WooCommerce pages, i.e. account and checkout, and custom 404 redirect on default login URLs.
* **Login ID Type**. Restrict login ID only to username or email address.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Login Page Customizer**. Easily customize the design of the login page.
* **Site Identity on Login Page**. Use the site icon and URL to replace the default WordPress logo on the login page.
* **Log In/Out Menu**. Enable log in, log out and dynamic log in/out menu item for addition to any navigation menu. [ASE Pro](https://www.wpase.com/rdme-to-web) allows customizing the "Log In" and "Log Out" text.
* **Registration Column**. Show users registration date. [ASE Pro](https://www.wpase.com/rdme-to-web) makes the column sortable.
* **Last Login Column**. Log last login datetime and show it in the users list table. [ASE Pro](https://www.wpase.com/rdme-to-web) makes the column sortable.
* **Redirect After Login / Logout**. Set custom redirect URL for all or some user roles after login / logout. [ASE Pro](https://www.wpase.com/rdme-to-web) allows setting separate redirect URL for each role.
* **Disable User Account**. Disable login for individual users while preserving their content and display name.
### Custom Code
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Code Snippets Manager**. Manage CSS / SCSS, JS, HTML and PHP code snippets to modify your site's content, design, behaviour and functionalities.
* **Custom Admin CSS**.
* **Custom Frontend CSS**.
* **Insert &lt;head&gt;, &lt;body&gt; and &lt;footer&gt; Code**. Easily insert &lt;meta&gt;, &lt;link&gt;, &lt;script&gt; and &lt;style&gt; tags for tracking, analytics, etc.
* **Custom Body Class**.
* **Manage ads.txt and app-ads.txt**.
* **Manage robots.txt**.
### Disable Components
* **Disable Gutenberg** for some or all applicable post types. Optionally disable frontend block assets. [ASE Pro](https://www.wpase.com/rdme-to-web) allows disabling only on / except on certain post types, or on all post types.
* **Disable Comments**. Disable comments for some or all public post types. [ASE Pro](https://www.wpase.com/rdme-to-web) allows disabling only on / except on certain post types, or on all post types.
* **Disable REST API**. Disable REST API access for non-authenticated users and remove URL traces from &lt;head&gt;, HTTP headers and WP RSD endpoint. [ASE Pro](https://www.wpase.com/rdme-to-web) allows enabling access only for some, authenticated user roles, and adds a way to exclude certain API routes.
* **Disable Feeds**. Disable all RSS, Atom and RDF feeds.
* **Disable Embeds**. Prevent your site content from being embedded on other sites. Prevent embedding of non-whitelisted sites in your site. Disable all, related JavaScript. Removes support for the embed block.
* **Disable All Updates**. Completely disable core, theme and plugin updates and auto-updates. Will also disable update checks, notices and emails.
* **Disable Author Archives**. Return 404 (Not Found) error when trying to load author archives. Remove or disable links to author archives. Remove authors archives from the sitemap.
* **Disable Smaller Components**. Prevent smaller components from running or loading. Those are generator &lt;meta&gt; tag, version number, Windows Live Writer (WLW) manifest &lt;link&gt; tag, Really Simple Discovery (RSD) &lt;link&gt; tag, WordPress shortlink &lt;link&gt; tag in &lt;head&gt;, and also dashicons CSS and JS files, emoji support, jQuery Migrate, block-based widgets settings screen, native lazy load of images, application passwords, site admin email verification screen, user email notification after password change and plugin / theme editor.
### Security
* **Limit Login Attempts**. Prevent brute force attacks by limiting the number of failed login attempts allowed per IP address. [ASE Pro](https://www.wpase.com/rdme-to-web) adds IP whitelisting, which is also useful to unblock users.
**[[ASE Pro](https://www.wpase.com/rdme-to-web)] CAPTCHA Protection**. Support for [ALTCHA](https://altcha.org/) self-hosted version (GDPR-compliant, open source, free), Google reCaptcha v2 and v3, and Cloudflare Turnstile for WordPress and WooCommerce default forms (login, password reset, registration and comment forms).
**[[ASE Pro](https://www.wpase.com/rdme-to-web)] Two-Factor Authentication (2FA)**. Apply 2FA for some or all user roles with grace period settings and the option to apply different 2FA settings for each user role. Supported methods are authenticator app (TOTP), recovery codes and email.
* **Obfuscate Author Slugs**. Obfuscate publicly exposed author page URLs that shows the user slugs / usernames.
* **Email Address Obfuscator**. Obfuscate email address to prevent spam bots from harvesting them,. [ASE Pro](https://www.wpase.com/rdme-to-web) enables output of obfuscated mailto: link and also auto-obfuscation of email addresses in post content.
* **Disable XML-RPC**. Protect your site from brute force, DOS and DDOS attacks via XML-RPC. Also disables trackbacks and pingbacks.
### Optimizations
* **Image Upload Control**. Resize newly uploaded, large images to a smaller dimension and delete originally uploaded files. BMPs and non-transparent PNGs will be converted to JPGs and resized. [ASE Pro](https://www.wpase.com/rdme-to-web) enables setting custom JPG conversion quality and adds an option for conversion to WebP with custom conversion quality. You can also disable generation of some or all intermediate sizes.
* **Revisions Control**. Limit the number of revisions to keep for some or all post types. [ASE Pro](https://www.wpase.com/rdme-to-web) enables revisions control "only for", "except for" or "for all" post types.
* **Heartbeat Control**. Modify the interval of the WordPress heartbeat API or disable it on admin pages, post creation/edit screens and/or the frontend.
### Utilities
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Site Backup and Migration**. Backup files and database, restore from backups, and migrate or sync to another server.
* **Email Delivery**. Set custom sender name and email. Optionally use external SMTP service to ensure notification and transactional emails from your site are being delivered to inboxes. [ASE Pro](https://www.wpase.com/rdme-to-web) adds the option to specify a custom reply-to name and email, Bcc address(es), disable authentication and the option to log email delivery.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Form Builder**. Enable the drag-and-drop creation of various types of forms (contact, feedback, booking, application, proposal, admission, support, survey, etc.) on the frontend to collect information from site visitors or users or members. 33 field types are available, including Net Promoter Score (NPS), Likert, Matrix of Uniform and Variable Dropdowns and CAPTCHA fields. Support custom form styles, multi-columns layout, email notification, auto responder, entries management and webhooks for sending submission data to Zapier, n8n, etc.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] File Manager**. A comprehensive file manager with folder tree navigation, file and folder operations, and code editing capabilities.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Local User Avatar**. Enable usage of any image from the media library as user avatars.
* **Multiple User Roles**. Assign multiple roles during user account creation and editing.
* **Image Sizes Panel**. Display a panel showing and linking to all available sizes when viewing an image in the media library. [ASE Pro](https://www.wpase.com/rdme-to-web) adds a copy button for the image URL on click.
* **View Admin as Role**. View admin pages and the site (logged-in) as one of the non-administrator user roles.
* **Password Protection**. Password-protect the entire site to hide the content from public view and search engine bots / crawlers. [ASE Pro](https://www.wpase.com/rdme-to-web) adds IP whitelisting and bypassing via URL parameter, and also applies design elements from the Login Page Customizer module.
* **Maintenance Mode**. Show a simple maintenance message. [ASE Pro](https://www.wpase.com/rdme-to-web) adds option to set custom page and browser tab title, use WYSIWYG editor, use pattern, image, solid color background, apply custom CSS, and also add the option to use an existing page created with the block / classic editor or a page builder as the maintenance page. It also adds an option to exclude certain URLs (wildcard support) and allow non-admins to access the frontend.
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Redirect Manager**. Easily manage various types of redirects / redirections (3xx, 4xx, 5xx codes) with support for wildcards and regular expressions (regex).
* **Redirect 404**. Perform 301 (permanent) redirect to the homepage for all 404 (not found) pages. [ASE Pro](https://www.wpase.com/rdme-to-web) adds option to set custom redirect URL.
* **Display System Summary**. Show web server software, PHP version, database software and server IP address in the "At a Glance" dashboard widget. [ASE Pro](https://www.wpase.com/rdme-to-web) also shows the size of the site, database and root, wp-content, plugins, themes and upload folders.
* **Search Engines Visibility Status**. Show admin bar status and admin notice when search engines are set to be discouraged from indexing the site. [ASE Pro](https://www.wpase.com/rdme-to-web) adds a way to prevent dev / staging sites from having search engine visibility enabled.
[**See all features >>**](https://www.wpase.com/features/)
### ASE Pro
**Lifetime Deal (LTD)** is available at [www.wpase.com](https://www.wpase.com/rdme-to-web).
### LANGUAGES
ASE has been translated into [30+ languages](https://translate.wpase.com). You can [help translate](https://translate.wpase.com) ASE into your language(s).
## Screenshots
1. Content Management
![Content Management](.wordpress-org/screenshot-1.png)
2. Admin Interface
![Admin Interface](.wordpress-org/screenshot-2.png)
3. Log In | Log Out
![Log In | Log Out](.wordpress-org/screenshot-3.png)
4. Custom Code
![Custom Code](.wordpress-org/screenshot-4.png)
5. Disable Components
![Disable Components](.wordpress-org/screenshot-5.png)
6. Security
![Security](.wordpress-org/screenshot-6.png)
7. Optimizations
![Optimizations](.wordpress-org/screenshot-7.png)
8. Utilities
![Utilities](.wordpress-org/screenshot-8.png)
9. Admin Menu Organizer module
![Admin Menu Organizer module](.wordpress-org/screenshot-9.png)
10. Email Delivery module
![Email Delivery module](.wordpress-org/screenshot-10.png)
## Frequently Asked Questions
### How Do I Reset ASE Settings / Configuration?
If for whatever reason you need to remove ASE and reset the settings / configuration, please follow these steps:
1. Access your website via FTP and delete ASE plugin folder at /wp-content/plugins/admin-site-enhancements/
2. Login to your site via the default URL, /wp-admin/ or /wp-login.php
3. Install [SQL Buddy](https://wordpress.org/plugins/sql-buddy/) plugin, open it, and click on the wp_options table
4. Use the small filter icon at the top of wp_options table rows, and filter for “option_name” => “contains” => “admin_site_enhancements”. You should get 3 options . Click on each one and hit the “Delete” icon in the bottom-right corner.
5. Re-install and re-configure ASE as needed.
You can skip step 1 and 2 if you still have access to your wp-admin dashboard.
### Does ASE Work With Multisite?
ASE does not officially support multisite. Please use at your own risk. That said, several users have reported that it works fine on their multisite when activated on each sub-site and not through network activation.
## Changelog
**Admin and Site Enhancements (ASE) v1.0.0** was released on October 17, 2022. Since then, there have been **87 _major_ releases** (e.g. 1.1.0 ) and **199 _minor_ releases** (e.g. 4.9.1), for a **total of 286 releases**.
Each **_major release_** usually corresponds with the addition of one new module/feature. Each module/feature usually is the equivalent of one (or more) single-purpose plugin. Each **_minor release_** usually contain one or more bugfix or improvements to existing modules/features.
[**Upgrade to ASE Pro**](https://www.wpase.com/chnlg-to-web). Lifetime Deal (LTD) available.
### 8.8.5 (2026.06.22) - ASE Free and Pro
* **[IMPROVED/FIXED in Free and Pro] Admin Interface >> Wider Admin Menu**: fixed an issue where the wider admin menu is overlapping Fluent Support admin pages. Props to [Diiamo](https://wordpress.org/support/users/luislu/) for [reporting](https://wordpress.org/support/topic/wider-admin-menu-issue/) this with a screenshot.
* **[IMPROVED/FIXED in Free and Pro] Utilities >> Email Delivery**: improved mechanism for encrypting SMTP password to no longer rely on WP salts, which can change in certain scenarios, causing silent email delivery failure. Props to someone from @rocket.ch for reporting the issue.
* **[FIXED in Free and Pro] Admin Interface >> Admin Menu Organizer**: fixed several PHP warnings in PHP 8.4.
* **[IMPROVED and FIXED in Pro] Utilities >> Site Backup and Migration**:
* Improved the reliability of database export during backups. Props to Uli L. for prompting this improvement.
* Improved the reliability of how the "Download" button works for local and remote backup archives. Props to Dominik K. for reporting a related issue that prompted this improvement.
* Storage folder (from the 'Storage' tab/feature) by default is no longer included when creating a backup archive. Props to Fellipe R.d.V. for prompting this change.
* Added a new option in the module settings to include the storage folder in backup, restore and migration operations. May be useful if you want to have extra redundancy for the WP site you are using as a storage location for backing up your other WP sites. For example, in that storage WP site's backup policy, you can opt to send a backup to Amazon S3, Dropbox, etc., thus creating redundancies for the backup archives of your other WP sites in one go.
* Added backup archive note viewer inside the emergency restore script.
* Fixed JS error related to the archive note when initiating manual backup.
* **[FIXED in Pro] Content Management >> Custom Content Types**: fixed PHP warning in PHP 8.4 related to the new WPML integration. Props to Niels S. for reporting the issue in detail.
* **[FIXED in Pro] Log In/Out & Register >> Redirect After Login**: fixed a bug where 2FA is being bypassed when this module is enabled. Props to Danny V. for reporting the issue in detail, with screenshots and a screen recording.
* **[TRANSLATION in Free and Pro]** ASE is now being translated into [38 languages](https://translate.wpase.com/):
* **Added new/improved translation** for:
* ASE Free: Updated Slovak, Portuguese (Brazil), Polish, Norwegian, Dutch (Netherlands), Dutch (Belgium).
* ASE Pro: Updated Greek, Polish, Slovak.
* **More strings have been internationalized**. @Translators, please visit the respective project pages for the Free and Pro versions to translate the new strings, if you havent' done so already.
* **Interested to help translate or improve the translation?** Please go to [https://translate.wpase.com](https://translate.wpase.com) for more info.
* **[Chinese (China)](https://translate.wordpress.org/locale/zh-cn/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@bricksvip](https://profiles.wordpress.org/bricksvip/) et al. Current status: [39 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/zh-cn/default/?filters%5Bstatus%5D=untranslated).
* **[Portuguese (Brazil)](https://translate.wordpress.org/locale/pt-br/default/wp-plugins/admin-site-enhancements/)**: ASE Free and ASE Pro (completed). Props to [Valdemir M.](https://profiles.wordpress.org/valdemirmaran/), [Dennis F.](https://profiles.wordpress.org/dnn/), [denisgomesfranco](https://profiles.wordpress.org/denisgomesfranco/), [Denison C.](https://profiles.wordpress.org/denisoncarlos/) and [@ofmarconi](https://profiles.wordpress.org/ofmarconi/). Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/pt-br/default/?filters%5Bstatus%5D=untranslated).
* **[Hungarian](https://translate.wordpress.org/locale/hu/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [R. József G.](https://profiles.wordpress.org/radicsjg/) et al. Current status: [14 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/hu/default/?filters%5Bstatus%5D=untranslated).
* **[Vietnamese](https://translate.wordpress.org/locale/vi/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Hoang N.Q.](https://profiles.wordpress.org/nguyenquanghoang/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/vi/default/?filters%5Bstatus%5D=untranslated).
* **[Italian](https://translate.wordpress.org/locale/it/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Fabio P.](https://profiles.wordpress.org/fabioperri/) et. al. Current status: [36 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/it/default/?filters%5Bstatus%5D=untranslated).
* **[Dutch](https://translate.wordpress.org/locale/nl/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Toine R](https://profiles.wordpress.org/toineenzo/) and [Peter S.](https://profiles.wordpress.org/psmits1567/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/nl/default/?filters%5Bstatus%5D=untranslated).
* **[Korean](https://translate.wordpress.org/locale/ko/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@chazmlab](https://profiles.wordpress.org/chazmlab/) et al. Current status: [8 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ko/default/?filters%5Bstatus%5D=untranslated).
* **[German (Formal)](https://translate.wordpress.org/locale/de/formal/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@markussss](https://profiles.wordpress.org/markussss/), Bastian S. et al. Current status: [63 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/de/formal/?filters%5Bstatus%5D=untranslated).
* **[French](https://translate.wordpress.org/locale/fr/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Stéphan G.](https://profiles.wordpress.org/gongonzo/), [@jeanfrancoisdelvin](https://profiles.wordpress.org/jeanfrancoisdelvin/), [@srossignol](https://profiles.wordpress.org/srossignol/), [@lucashw](https://profiles.wordpress.org/lucashw/), [@skippy43](https://profiles.wordpress.org/skippy43/), [@anlip](https://profiles.wordpress.org/anlip/), [@agencefacton](https://profiles.wordpress.org/agencefacton/), [@injsbx](https://profiles.wordpress.org/injsbx/) et al. Current status: [25 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/fr/default/?filters%5Bstatus%5D=untranslated).
* **[Polish](https://translate.wordpress.org/locale/pl/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Jaroslaw K.](https://profiles.wordpress.org/kosmity/), [Dziubek](https://profiles.wordpress.org/dziubek/) and [Dariusz Z.](https://profiles.wordpress.org/dariobros/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/pl/default/?filters%5Bstatus%5D=untranslated).
* **[Norwegian](https://translate.wordpress.org/locale/nb/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Alf O.F.](https://profiles.wordpress.org/skoen/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/nb/default/?filters%5Bstatus%5D=untranslated).
* **[Czech](https://translate.wordpress.org/locale/cs/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to Jan S. et al. Current status: [59 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/cs/default/?filters%5Bstatus%5D=untranslated).
* **[Spanish (Spain)](https://translate.wordpress.org/locale/es/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@marcorubiol](https://profiles.wordpress.org/marcorubiol/) et al. Current status: [34 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/es/default/?filters%5Bstatus%5D=untranslated).
* **[Slovak](https://translate.wordpress.org/locale/sk/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Dominik K.](https://profiles.wordpress.org/dominokozmali/) et al. Current status: [8 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sk/default/?filters%5Bstatus%5D=untranslated).
* **[Indonesian](https://translate.wordpress.org/locale/id/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@pakacil](https://profiles.wordpress.org/pakacil/), [Wawan S.](https://profiles.wordpress.org/ahmad-rafiansyah/) et al. Current status: [10 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/id/default/?filters%5Bstatus%5D=untranslated).
* **[Portuguese (Portugal)](https://translate.wordpress.org/locale/pt/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Ricardo C.](https://profiles.wordpress.org/madebyuh/) et al. Current status: [37 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/pt/default/?filters%5Bstatus%5D=untranslated).
* **[Ukrainian](https://translate.wordpress.org/locale/uk/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Irina](https://profiles.wordpress.org/irinashl/) et al. Current status: [23 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/uk/default/?filters%5Bstatus%5D=untranslated).
* **[Urdu](https://translate.wordpress.org/locale/ur/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Ayyaz A.](https://profiles.wordpress.org/ayyazahmad/) et al. Current status: [135 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ur/default/?filters%5Bstatus%5D=untranslated).
* **[Swedish](https://translate.wordpress.org/locale/sv/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Robert M.](https://profiles.wordpress.org/robertmichalski/) and [Tor-Bjorn F.](https://profiles.wordpress.org/tobifjellner/). Current status: [36 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sv/default/?filters%5Bstatus%5D=untranslated).
* **[Danish](https://translate.wordpress.org/locale/da/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Morten E.L.](https://profiles.wordpress.org/ellegaarddk/), [Helgi P.](https://profiles.wordpress.org/helgipetersen/) and [Kurt M.A.](https://profiles.wordpress.org/moskjaer/) et al. Current status: [23 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/da/default/?filters%5Bstatus%5D=untranslated).
* **[Arabic](https://translate.wordpress.org/locale/ar/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (completed). Props to [Mohammed J.](https://profiles.wordpress.org/ih4xz/), Yaser M., et al. Current status: [10 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ar/default/?filters%5Bstatus%5D=untranslated).
* **[Romanian](https://translate.wordpress.org/locale/ro/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Dan C.](https://profiles.wordpress.org/dancaragea/), [@ravishi](https://profiles.wordpress.org/ravishi/) and [@catalinx777](https://profiles.wordpress.org/catalinx777/). Current status: [19 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ro/default/?filters%5Bstatus%5D=untranslated).
* **[Turkish](https://translate.wordpress.org/locale/tr/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@saeead](https://wordpress.org/support/users/saeead/), [@serdaroztrk](https://profiles.wordpress.org/serdaroztrk/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/tr/default/?filters%5Bstatus%5D=untranslated).
* **[Chinese (Taiwan)](https://translate.wordpress.org/locale/zh-tw/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@gordon168](https://profiles.wordpress.org/gordon168/) and [Hedula](https://profiles.wordpress.org/hedula/). Current status: [1 strings untranslated](https://translate.wordpress.org/locale/zh-tw/default/wp-plugins/admin-site-enhancements/)
* **[Spanish (Chile)](https://translate.wordpress.org/locale/es-cl/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@srgio](https://profiles.wordpress.org/srgio/). Current status: [35 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/es-cl/default/?filters%5Bstatus%5D=untranslated).
* **[Serbian](https://translate.wordpress.org/locale/sr/default/wp-plugins/admin-site-enhancements/)**: ASE Free (partial). Props to [Igor E.](https://wordpress.org/support/users/igorel/) and [@plug-n-play](https://profiles.wordpress.org/plug-n-play/). Current status: [17 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sr/default/?filters%5Bstatus%5D=untranslated).
* **[Albanian](https://translate.wordpress.org/locale/sq/default/wp-plugins/admin-site-enhancements/)**: Props to [@algertpateqi](https://profiles.wordpress.org/algertpateqi/). Current status: [34 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sq/default/?filters%5Bstatus%5D=untranslated).
* **[Persian](https://translate.wordpress.org/locale/fa/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@saeead](https://profiles.wordpress.org/saeead/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/fa/default/?filters%5Bstatus%5D=untranslated).
* **[Russian](https://translate.wordpress.org/locale/ru/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@sergey369](https://profiles.wordpress.org/sergey369/), [@pfgr](https://profiles.wordpress.org/pfgr/) et al. Current status: [26 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ru/default/?filters%5Bstatus%5D=untranslated).
* **[Bosnian](https://translate.wordpress.org/locale/bs/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@plug-n-play](https://profiles.wordpress.org/plug-n-play/) et al. Current status: [76 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/bs/default/?filters%5Bstatus%5D=untranslated).
* **[Croatian](https://translate.wordpress.org/locale/hr/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@plug-n-play](https://profiles.wordpress.org/plug-n-play/). Current status: [81 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/hr/default/?filters%5Bstatus%5D=untranslated).
* **[Japanese](https://translate.wordpress.org/locale/ja/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@zionote](https://profiles.wordpress.org/zionote/) et al. Current status: [38 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ja/default/?filters%5Bstatus%5D=untranslated).
* **[Catalan](https://translate.wordpress.org/locale/ca/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@agendablanca](https://profiles.wordpress.org/agendablanca/), [@lluishospital](https://profiles.wordpress.org/lluishospital/) et al. Current status: [49 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ca/default/?filters%5Bstatus%5D=untranslated).
* **[Bengali (Bangladesh)](https://translate.wordpress.org/locale/bn/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (partial). Props to [Ruhani R.](https://profiles.wordpress.org/ruhanirabin/). Current status: [37 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/bn/default/?filters%5Bstatus%5D=untranslated).
* **[Bulgarian](https://translate.wordpress.org/locale/bg/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (partial). Props to [Svetoslav S.](https://sslavkov.eu). Current status, [18 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/bg/default/?filters%5Bstatus%5D=untranslated).
* **[Greek](https://translate.wordpress.org/locale/el/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (completed). Props to [Stelio C.](https://profiles.wordpress.org/scprojectsgr/). Current status: [38 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/el/default/?filters%5Bstatus%5D=untranslated).
* **[Dutch (Belgium)](https://translate.wordpress.org/locale/nl-be/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [Pieterjan D.](https://profiles.wordpress.org/nekojonez/). Current status, [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/nl-be/default/?filters%5Bstatus%5D=untranslated).
* **[Azerbaijani](https://translate.wordpress.org/locale/az/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [Saeid BGrn](https://profiles.wordpress.org/saeead/). Current status, [1 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/az/default/?filters%5Bstatus%5D=untranslated).
* **[Estonian](https://translate.wordpress.org/locale/et/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (completed). Props to [Aleksander K.](https://profiles.wordpress.org/itagentuur/). Current status, [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/et/default/?filters%5Bstatus%5D=untranslated).
[**See the full changelog >>**](https://www.wpase.com/documentation/changelog/)
@@ -0,0 +1,136 @@
<?php
/**
* Plugin Name: Admin and Site Enhancements (ASE)
* Plugin URI: https://www.wpase.com/plugin-uri
* Description: Easily enable enhancements and features that usually require multiple plugins.
* Version: 8.8.5
* Author: wpase.com
* Author URI: https://www.wpase.com/author-uri
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
*/
// If this file is called directly, abort.
if ( !defined( 'ABSPATH' ) ) {
exit;
}
define( 'ASENHA_VERSION', '8.8.5' );
define( 'ASENHA_ID', 'asenha' );
define( 'ASENHA_SLUG', 'admin-site-enhancements' );
define( 'ASENHA_SLUG_U', 'admin_site_enhancements' );
define( 'ASENHA_URL', plugins_url( '/', __FILE__ ) ); // e.g. https://www.example.com/wp-content/plugins/this-plugin/
define( 'ASENHA_PATH', plugin_dir_path( __FILE__ ) ); // e.g. /home/user/apps/wp-root/wp-content/plugins/this-plugin/
// define( 'ASENHA_BASE', plugin_basename( __FILE__ ) ); // e.g. plugin-slug/this-file.php
// define( 'ASENHA_FILE', __FILE__ ); // /home/user/apps/wp-root/wp-content/plugins/this-plugin/this-file.php
/**
* Autoload classes defined by this plugin
*
* @param string $class_name e.g. \ASENHA\Classes\The_Name
* @since 1.0.0
*/
function asenha_autoloader( $class_name ) {
$namespace = 'ASENHA';
// Only process classes within this plugin's namespace
if ( false !== strpos( $class_name, $namespace ) ) {
// Assemble file path where class is defined
// \ASENHA\Classes\The_Name => \Classes\The_Name
$path = str_replace( $namespace, "", $class_name );
// \Classes\The_Name => /classes/the_name
$path = str_replace( "\\", DIRECTORY_SEPARATOR, strtolower( $path ) );
// /classes/the_name => /classes/the-name.php
$path = str_replace( "_", "-", $path ) . '.php';
// /classes/the-name.php => /classes/class-the-name.php
$path = str_replace( "classes" . DIRECTORY_SEPARATOR, "classes" . DIRECTORY_SEPARATOR . "class-", $path );
// Remove first '/'
$path = substr( $path, 1 );
// Get /plugin-path/classes/class-the-name.php
$path = ASENHA_PATH . $path;
if ( file_exists( $path ) ) {
require_once( $path );
}
}
}
// Register autoloading classes
spl_autoload_register( 'asenha_autoloader' );
/**
* Code that runs on plugin activation
*
* @since 1.0.0
*/
function asenha_on_activation() {
$activation = new ASENHA\Classes\Activation;
$activation->create_failed_logins_log_table();
$options = get_option( ASENHA_SLUG_U, array() );
if ( array_key_exists( 'disable_embeds', $options ) && $options['disable_embeds'] ) {
$options['disable_embeds_flush_rewrite_rules_needed'] = true;
}
update_option( ASENHA_SLUG_U, $options, true );
}
/**
* Code that runs on plugin deactivation
*
* @since 1.0.0
*/
function asenha_on_deactivation() {
$deactivation = new ASENHA\Classes\Deactivation;
$deactivation->clear_failed_login_attempts_log_cleanup_schedule();
$deactivation->delete_failed_logins_log_table();
$options = get_option( ASENHA_SLUG_U, array() );
if ( array_key_exists( 'disable_embeds', $options ) && $options['disable_embeds'] ) {
$deactivation->disable_embeds_flush_rewrite_rules();
}
}
// Register code that runs on plugin activation
register_activation_hook( __FILE__, 'asenha_on_activation');
// Register code that runs on plugin deactivation
register_deactivation_hook( __FILE__, 'asenha_on_deactivation' );
// Load translations
function asenha_free_load_textdomain() {
load_plugin_textdomain( 'admin-site-enhancements', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action( 'init', 'asenha_free_load_textdomain' );
// https://make.wordpress.org/core/2024/10/21/i18n-improvements-6-7/
// Use when tracing which code is triggering the "_load_textdomain_just_in_time Called Incorrectly" notice
// add_action(
// 'doing_it_wrong_run',
// static function ( $function_name ) {
// if ( '_load_textdomain_just_in_time' === $function_name ) {
// debug_print_backtrace();
// }
// }
// );
// Functions for setting up admin menu, admin page, the settings sections and fields and other fondational stuff
require_once ASENHA_PATH . 'settings.php';
// Other required functions
require_once ASENHA_PATH . 'functions.php';
// Load vendor libraries
// require_once ASENHA_PATH . 'vendor/autoload.php';
// Bootstrap all the functionalities of this plugin
require_once ASENHA_PATH . 'bootstrap.php';
@@ -0,0 +1,180 @@
.wrap.admin-menu-organizer {
margin: 10px 20px 0;
}
.admin-menu-organizer-main {
display: flex;
gap: 16px;
}
.module-description {
margin-top: 8px;
margin-bottom: 24px;
}
.admin-menu-sortables-wrapper {
width: 696px;
box-sizing: border-box;
margin-top: 16px;
padding: 32px 48px 32px;
background: #fff;
}
.admin-menu-actions {
position: relative;
display: flex;
flex-direction: row;
align-items: flex-start;
}
.admin-menu-actions #amo-save-changes {
position: sticky;
margin-top: 16px;
top: 48px;
border-style: solid;
border-color: #fff;
border-width: 24px 40px;
}
.admin-menu-actions #amo-save-changes:focus {
box-shadow: none !important;
}
.asenha-saving-changes,
.asenha-changes-saved {
position: sticky;
}
.asenha-saving-changes {
top: 76px;
margin-top: 44px;
margin-left: -31px;
}
.asenha-changes-saved {
top: 74px;
margin-top: 42px;
margin-left: -33px;
}
.admin-menu-organizer ul.menu {
margin-top: 0;
padding-top: 0;
/* overflow: auto; */
}
.admin-menu-organizer .menu-item-bar {
display: flex;
align-items: center;
margin: 8px 0 0;
}
.admin-menu-organizer .menu-item-bar .menu-item-handle {
box-sizing: border-box;
display: flex;
align-items: center;
padding: 3px 15px 3px 7px;
max-width: 600px;
width: 600px;
min-height: 30px;
}
.admin-menu-organizer .title-wrapper {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 4px;
}
.admin-menu-organizer .menu-item-handle .menu-item-checkbox {
display: inline-block;
}
.admin-menu-organizer .menu-item-handle .item-title {
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
margin-right: 0;
line-height: 30px;
}
.rtl .admin-menu-organizer .menu-item-handle .item-title {
}
.admin-menu-organizer .menu-item-handle .item-title .menu-item-title a {
color: #666;
text-decoration: none;
margin-left: 6px;
}
.admin-menu-organizer .menu-item-handle .item-title .menu-item-title a:hover {
color: inherit;
}
.menu-item-title input[type="text"],
.item-title .title-wrapper input[type="text"] {
border: 1px solid #dcdcde;
margin-left: -5px;
padding: 0 8px 0 4px;
font-size: 13px;
min-height: 28px;
line-height: 28px;
}
.menu-item-title input[type="text"]:focus,
.item-title .title-wrapper input[type="text"]:focus {
border: 1px solid #999;
box-shadow: none;
outline: none;
}
.menu-item-checkbox-label span {
font-weight: normal;
}
#separator1 .menu-item-title,
#separator2 .menu-item-title,
#separator-last .menu-item-title,
#separator-woocommerce .menu-item-title,
li[id^="separator"] .menu-item-title {
font-weight: normal;
color: #999;
}
/* Scoped to AMO root list so other screens (e.g. ABCE) can style .sortable-placeholder independently. */
#custom-admin-menu .sortable-placeholder {
width: 600px !important;
max-width: 600px !important;
height: 46px;
min-height: 42px;
max-height: 42px;
background: #cdddd6;
border: 1px dashed #508871;
border-radius: 0 !important;
}
.form-table .asenha-subfields td:not(.datatable-td) label.menu-item-checkbox-label {
top: 0;
}
#custom-admin-menu .menu-item-depth-0 {
display: list-item !important;
margin-block-start: 0;
padding: 0;
position: relative;
}
#custom-admin-menu .parent-menu-item .dashicons-menu {
padding: 5px 8px;
cursor: move;
color: #ddd;
}
#custom-admin-menu .menu-item-bar .menu-item-handle:hover .dashicons-menu {
color: #1d2327;
}
.submenu-sortable li.menu-item {
position: relative;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,165 @@
/* Styling for Content Management >> Content Order pages */
.page-header {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.page-header h2 {
font-size: 23px;
font-weight: 400;
margin: 0;
padding: 9px 0 4px;
line-height: 1.3;
}
#screen-meta-links {
display: none;
}
#toggles {
display: none;
}
#toggle-excerpt {
position: relative;
top: 2px;
}
#toggles input[type="checkbox"] {
margin-left: 8px;
}
#toggles label {
font-size: 14px;
}
#item-list {
margin-bottom: -20px;
}
#item-list.asenha-content-order li.list-item {
position: relative;
clear: both;
overflow: hidden;
margin-bottom: 0;
padding: 0;
background: none;
box-shadow: none;
}
.row.ui-sortable-handle {
background: #fff;
margin-top: 3px;
margin-bottom: 3px;
border: 1px solid #c3c4c7;
}
.row-content {
display: flex;
align-items: center;
position: relative;
padding: 11px 16px 9px;
font-size: 14px;
font-weight: 700;
cursor: move;
overflow: hidden;
}
.content-main {
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
}
.content-additional {
display: flex;
align-items: center;
height: 100%;
position: absolute;
top: 0;
right: 8px;
}
.row-content:hover {
background: #fff6e5;
}
.ui-sortable-placeholder {
border-radius: 0 !important;
background: #cdddd6 !important;
border: 1px dashed #508871 !important;
visibility: visible !important;
}
.row-content .dashicons.dashicons-menu {
position: relative;
top: -1px;
margin-right: 4px;
}
.row-content:hover .dashicons.dashicons-menu {
color: inherit;
}
a.item-title,
a.item-view-link {
text-decoration: none;
}
a.item-title:hover {
text-decoration: underline;
}
.item-status-hidden,
.wp-core-ui .button.item-view-link {
display: none;
}
.wp-core-ui .row-content:hover .button.item-view-link {
display: inherit;
display: flex;
}
.wp-core-ui .button.item-view-link {
font-weight: normal;
box-shadow: 0 0 8px 8px #fff6e5;
height: 30px;
max-height: 30px;
min-height: 30px;
line-height: 28px;
}
.has-child-label {
}
.content-main .fader {
position: absolute;
top: 1px;
right: 1px;
width: 48px;
height: 38px;
background: ;
background-image: linear-gradient(to right, rgba(255,255,255,0) , #ffffff);
}
.row-content:hover .content-main .fader {
background-image: linear-gradient(to right, rgba(255,246,229,0) , #fff6e5);
}
.updating-order-notice {
box-sizing: border-box;
position: absolute;
right: 8px;
z-index: 10;
height: 32px;
display: flex;
align-items: center;
background: #eee;
padding: 6px 12px 6px 8px;
border: 1px solid #c3c4c7;
}
.spinner-img {
width: 16px;
height: 16px;
margin: 2px 6px 2px 2px;
}
.updating-order-notice .dashicons.dashicons-saved {
color: green;
margin-right: 4px;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
.asenha-content-duplication-status-info .components-button.is-secondary {
margin-top: 8px;
}
@@ -0,0 +1,27 @@
.jBox-Modal.admin-notices-modal .jBox-container {
height: 100vh;
border-radius: 0;
background: #f0f0f1;
}
.admin-notices-modal.jBox-closeButton-box .jBox-closeButton {
top: 0;
right: 0;
background: none;
}
.admin-notices-modal.jBox-closeButton-box .jBox-closeButton svg {
width: 16px;
height: 16px;
margin-top: 0;
margin-right: -2px;
}
.jBox-Modal.admin-notices-modal .jBox-content {
padding: 8px 28px 32px;
}
.asenha-admin-notices-drawer > div {
margin: 0 0 16px 0;
padding: 12px;
}
@@ -0,0 +1,74 @@
/* Adapted from https://plugins.trac.wordpress.org/browser/image-sizes-panel/tags/0.4/admin/css/admin.css */
#image_sizes .inside {
margin: 0px;
padding: 0px;
}
#image_sizes table {
border: none;
border-collapse: collapse;
width: 100%;
}
#image_sizes table th {
display: none;
}
#image_sizes table td {
border-bottom: 1px solid #eee;
padding: 6px 12px;
text-align: left;
vertical-align: top;
}
#image_sizes table .size .name {
font-weight: bold;
display: block;
max-width: 135px;
min-width: 1px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#image_sizes table .dim {
padding-left: 0px;
text-align: right;
white-space: nowrap;
}
#image_sizes table td.copy {
padding-right: 0px;
width: 20px;
}
#image_sizes table td .copy-url-button {
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
}
#image_sizes table td .copy-url-button:hover {
cursor: pointer;
}
#image_sizes table td .copy-url-button:hover svg g {
stroke: #135e96;
}
#image_sizes .image-size-row {
position: relative;
}
#image_sizes .image-size-row .url-copied {
position: absolute;
top: 4px;
left: 40px;
background: #fff;
border: 1px solid #ccc;
padding: 2px 8px;
border-radius: 3px;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
/* Content Admin >> Show IDs */
#asenha-id {
width: 50px;
}
/* Content Admin >> Show Featured Images */
#asenha-featured-image {
width: 110px;
}
.asenha-featured-image img {
width: 110px;
height: auto;
border: 1px solid #e5e5e5;
}
/* Content Management >> Enable SVG Upload */
table.media .column-title .media-icon img[src*='.svg'] {
width: 100%;
height: auto;
}
/* Show ID in Action Row */
.row-actions .asenha-list-table-item-id {
color: #50575e;
}
@@ -0,0 +1,14 @@
#content-order-button {
display: inline;
margin-left: 5px;
}
.filter-items #content-order-button {
display: inline-block;
vertical-align: middle;
}
#content-order-button a {
padding-left: 12px;
padding-right: 12px;
}
@@ -0,0 +1,95 @@
/* CSS styles here are for the maintenance page set through Utilities >> Maintenance Mode */
* {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
margin: 0px;
padding: 0px;
}
body {
background-color: #ffffff;
background-attachment: fixed;
background-size: cover;
}
.page-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
/* background-image: linear-gradient(to bottom, #ffffff, #eeeeee, #dddddd, #cccccc, #bbbbbb); */
}
.page-overlay {
position: absolute;
display: block;
z-index: 0;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.message-box {
position: relative;
display: flex;
z-index: 1;
flex-direction: column;
align-items: center;
width: 50%;
font-family: "Arial", "Helvetica", sans-serif;
text-align: center;
}
.message-box h1 {
font-size: 3em;
font-weight: 500;
margin-top: 0;
margin-bottom: 0;
}
.message-box h1 p {
margin-top: 0;
margin-bottom: 0;
}
.message-box .description {
font-size: 1.25em;
line-height: 1.7;
}
@media screen and (max-width: 920px) {
.message-box {
width: 100%;
padding: 0 15%;
}
.message-box h1 {
font-size: 2em;
}
}
@media screen and (max-width: 480px) {
.message-box {
width: 100%;
padding: 0 40px;
}
.message-box h1 {
font-size: 1.5em;
}
.message-box .description {
font-size: 1em;
}
}
@@ -0,0 +1,14 @@
.media-modal-content .media-frame .media-toolbar select.attachment-filters {
padding-left: 8px;
padding-right: 8px;
}
.media-modal-content .media-frame .media-toolbar select#media-attachment-orderby.attachment-filters,
.media-modal-content .media-frame .media-toolbar select#media-attachment-order.attachment-filters {
display: none;
}
form.compat-item table.compat-attachment-fields tr td.field div.categorydiv div.tabs-panel {
margin-top: 4px !important;
margin-bottom: 8px;
}
@@ -0,0 +1,69 @@
/* Hide media replace compat field in media modals (grid replace is Pro; shown on upload.php below). */
.compat-field-asenha-media-replace {
display: none !important;
}
.wp-core-ui .button-secondary.asenha-media-replace-button {
margin-top: 8px;
}
.asenha-media-replace-notes {
margin-top: 8px;
}
.asenha-media-replace-notes p:last-child {
margin-bottom: 0;
}
.compat-item .compat-field-asenha-media-replace th.label {
display: none;
min-width: 0%;
width: 0%;
}
.compat-item .compat-field-asenha-media-replace .field {
float: left;
width: 99.8%;
}
.compat-field-asenha-media-replace .postbox {
margin-bottom: 0;
}
.compat-field-asenha-media-replace .postbox .inside {
margin-top: 8px;
margin-bottom: 0;
padding-bottom: 0;
}
.media-modal .compat-field-asenha-media-replace .postbox .inside {
padding-bottom: 12px;
}
#media-replace-div .postbox .inside {
padding-bottom: 0;
}
.compat-field-asenha-media-replace .postbox-header .hndle {
font-size: 1.125em;
line-height: 1;
padding-left: 12px;
padding-right: 12px;
}
.mime-type-warning {
font-size: 13px;
height: 32px;
line-height: 32px;
margin-top: 15px;
color: red;
font-weight: 700;
}
.media-frame-toolbar .media-toolbar-primary {
display: flex;
flex-direction: row;
align-items: center;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
body {
font-size: 13px;
}
@@ -0,0 +1,152 @@
/* Admin Interface >> Hide Howdy */
#wp-admin-bar-my-account.no-avatar > .ab-item:before {
display: none;
}
/* Content Duplication */
.additional-actions {
display: flex;
flex-wrap: wrap;
padding: 6px 10px 8px;
clear: both;
}
.additional-actions #duplication:before,
.additional-actions #public-preview:before {
content: "\f186";
position: relative;
top: -1px;
font: normal 20px / 1 dashicons;
color: #8c8f94;
speak: never;
display: inline-block;
margin-left: -1px;
padding-right: 6px;
vertical-align: top;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.additional-actions #public-preview:before {
content: "\f11f";
}
.asenha-public-preview-row {
align-items: center;
gap: 2px;
}
.additional-actions.asenha-public-preview-row {
padding: 6px 10px 4px;
}
.asenha-public-preview-row .asenha-public-preview-reset,
.asenha-public-preview-row .asenha-copy-preview-link-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 4px;
margin: 0;
min-height: 0;
line-height: 1;
vertical-align: middle;
text-decoration: none;
}
.asenha-public-preview-row .asenha-copy-preview-link-button {
margin-left: 4px;
}
.asenha-public-preview-row .asenha-public-preview-reset .dashicons {
font-size: 18px;
width: 18px;
height: 18px;
}
.asenha-public-preview-row .asenha-public-preview-icon-group {
display: inline-flex;
align-items: center;
gap: 0;
}
#misc-publishing-actions .asenha-public-preview-row .asenha-copy-preview-link-button {
color: var(--wp-admin-theme-color, #2271b1);
}
#misc-publishing-actions .asenha-public-preview-row .asenha-copy-preview-link-button:hover {
color: var(--wp-admin-theme-color-darker-10, #135e96);
}
#misc-publishing-actions .asenha-public-preview-row .asenha-copy-preview-link-button svg {
display: block;
width: 18px;
height: 18px;
}
#misc-publishing-actions .asenha-public-preview-expiry {
margin: 0;
padding: 0 10px 8px 35px;
font-size: 12px;
line-height: 1.4;
color: #646970;
clear: both;
}
/* Utilities >> Enable Login Logout */
.accordion-section #login-logout-links-checklist.categorychecklist {
margin: -13px 0 13px 0;
}
/* Content Management >> Enable External Permalink */
/* Custom Code >> Custom Body Class */
.external-permalink-input,
.custom-body-class-input {
padding: 8px 0 0;
}
.external-permalink-input-description,
.custom-body-class-input-description {
margin-top: 4px;
}
/* Utilities >> Multiple User Roles */
#adduser .form-field input[type="checkbox"], #createuser .form-field input[type="checkbox"] {
width: 1rem;
}
/* Utilities >> Display System Summary */
#dashboard_right_now .sub {
color: inherit !important;
background: none;
border-top: none;
margin-top: -8px;
padding: 0 12px 12px !important;
}
#dashboard_right_now .inside .sub > .system-summary {
margin-top: 8px;
padding: 8px 0 0 0 !important;
border-top: 1px solid #ddd;
}
/* General */
.asenha-warning-message {
font-weight: 500;
color: #d63638;
}
/* Utilities >> View Admin as Role */
#role-view-reset {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 999999;
box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
}
@@ -0,0 +1,2 @@
/* Styles loaded on all block editor instances (post.php / post-new.php) */
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,17 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#3c434a">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".3" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 697 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path fill="currentColor" d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"><animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></svg>

After

Width:  |  Height:  |  Size: 533 B

@@ -0,0 +1,62 @@
(function( $ ) {
'use strict';
$(document).ready( function() {
var strings = ( typeof amoPageVars !== 'undefined' && amoPageVars.strings ) ? amoPageVars.strings : {};
// Save changes
$('#amo-save-changes').click(function(e) {
e.preventDefault();
$('.asenha-saving-changes').fadeIn();
var menu_data = {
'action':'save_admin_menu',
'nonce': amoPageVars.saveMenuNonce,
'custom_menu_order': document.getElementById('custom_menu_order').value,
'custom_menu_titles': document.getElementById('custom_menu_titles').value,
'custom_menu_hidden': document.getElementById('custom_menu_hidden').value
}
$.ajax({
type: "post",
url: ajaxurl,
dataType: 'json',
data: menu_data,
success:function(response) {
$('.asenha-saving-changes').hide();
if ( ! response || response.success !== true ) {
if ( strings.saveChangesError ) {
window.alert( strings.saveChangesError );
}
return;
}
$('.asenha-changes-saved').fadeIn(400).delay(2500).fadeOut(400);
},
error:function(jqXHR) {
var message = strings.saveChangesError || '';
$('.asenha-saving-changes').hide();
if ( jqXHR && jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message ) {
message = jqXHR.responseJSON.data.message;
}
if ( message ) {
window.alert( message );
}
console.log(jqXHR);
}
});
});
}); // END OF $(document).ready()
})( jQuery );
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,862 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("css", function(config, parserConfig) {
var inline = parserConfig.inline
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
var indentUnit = config.indentUnit,
tokenHooks = parserConfig.tokenHooks,
documentTypes = parserConfig.documentTypes || {},
mediaTypes = parserConfig.mediaTypes || {},
mediaFeatures = parserConfig.mediaFeatures || {},
mediaValueKeywords = parserConfig.mediaValueKeywords || {},
propertyKeywords = parserConfig.propertyKeywords || {},
nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
fontProperties = parserConfig.fontProperties || {},
counterDescriptors = parserConfig.counterDescriptors || {},
colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {},
allowNested = parserConfig.allowNested,
lineComment = parserConfig.lineComment,
supportsAtComponent = parserConfig.supportsAtComponent === true,
highlightNonStandardPropertyKeywords = config.highlightNonStandardPropertyKeywords !== false;
var type, override;
function ret(style, tp) { type = tp; return style; }
// Tokenizers
function tokenBase(stream, state) {
var ch = stream.next();
if (tokenHooks[ch]) {
var result = tokenHooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == "@") {
stream.eatWhile(/[\w\\\-]/);
return ret("def", stream.current());
} else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
return ret(null, "compare");
} else if (ch == "\"" || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "#") {
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "hash");
} else if (ch == "!") {
stream.match(/^\s*\w*/);
return ret("keyword", "important");
} else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (ch === "-") {
if (/[\d.]/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (stream.match(/^-[\w\\\-]*/)) {
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ret("variable-2", "variable-definition");
return ret("variable-2", "variable");
} else if (stream.match(/^\w+-/)) {
return ret("meta", "meta");
}
} else if (/[,+>*\/]/.test(ch)) {
return ret(null, "select-op");
} else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
return ret("qualifier", "qualifier");
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch);
} else if (stream.match(/^[\w-.]+(?=\()/)) {
if (/^(url(-prefix)?|domain|regexp)$/i.test(stream.current())) {
state.tokenize = tokenParenthesized;
}
return ret("variable callee", "variable");
} else if (/[\w\\\-]/.test(ch)) {
stream.eatWhile(/[\w\\\-]/);
return ret("property", "word");
} else {
return ret(null, null);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, ch;
while ((ch = stream.next()) != null) {
if (ch == quote && !escaped) {
if (quote == ")") stream.backUp(1);
break;
}
escaped = !escaped && ch == "\\";
}
if (ch == quote || !escaped && quote != ")") state.tokenize = null;
return ret("string", "string");
};
}
function tokenParenthesized(stream, state) {
stream.next(); // Must be '('
if (!stream.match(/^\s*[\"\')]/, false))
state.tokenize = tokenString(")");
else
state.tokenize = null;
return ret(null, "(");
}
// Context management
function Context(type, indent, prev) {
this.type = type;
this.indent = indent;
this.prev = prev;
}
function pushContext(state, stream, type, indent) {
state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
return type;
}
function popContext(state) {
if (state.context.prev)
state.context = state.context.prev;
return state.context.type;
}
function pass(type, stream, state) {
return states[state.context.type](type, stream, state);
}
function popAndPass(type, stream, state, n) {
for (var i = n || 1; i > 0; i--)
state.context = state.context.prev;
return pass(type, stream, state);
}
// Parser
function wordAsValue(stream) {
var word = stream.current().toLowerCase();
if (valueKeywords.hasOwnProperty(word))
override = "atom";
else if (colorKeywords.hasOwnProperty(word))
override = "keyword";
else
override = "variable";
}
var states = {};
states.top = function(type, stream, state) {
if (type == "{") {
return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) {
return popContext(state);
} else if (supportsAtComponent && /@component/i.test(type)) {
return pushContext(state, stream, "atComponentBlock");
} else if (/^@(-moz-)?document$/i.test(type)) {
return pushContext(state, stream, "documentTypes");
} else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {
return pushContext(state, stream, "atBlock");
} else if (/^@(font-face|counter-style)/i.test(type)) {
state.stateArg = type;
return "restricted_atBlock_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {
return "keyframes";
} else if (type && type.charAt(0) == "@") {
return pushContext(state, stream, "at");
} else if (type == "hash") {
override = "builtin";
} else if (type == "word") {
override = "tag";
} else if (type == "variable-definition") {
return "maybeprop";
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
} else if (type == ":") {
return "pseudo";
} else if (allowNested && type == "(") {
return pushContext(state, stream, "parens");
}
return state.context.type;
};
states.block = function(type, stream, state) {
if (type == "word") {
var word = stream.current().toLowerCase();
if (propertyKeywords.hasOwnProperty(word)) {
override = "property";
return "maybeprop";
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
return "maybeprop";
} else if (allowNested) {
override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
return "block";
} else {
override += " error";
return "maybeprop";
}
} else if (type == "meta") {
return "block";
} else if (!allowNested && (type == "hash" || type == "qualifier")) {
override = "error";
return "block";
} else {
return states.top(type, stream, state);
}
};
states.maybeprop = function(type, stream, state) {
if (type == ":") return pushContext(state, stream, "prop");
return pass(type, stream, state);
};
states.prop = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
if (type == "}" || type == "{") return popAndPass(type, stream, state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "hash" && !/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(stream.current())) {
override += " error";
} else if (type == "word") {
wordAsValue(stream);
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
}
return "prop";
};
states.propBlock = function(type, _stream, state) {
if (type == "}") return popContext(state);
if (type == "word") { override = "property"; return "maybeprop"; }
return state.context.type;
};
states.parens = function(type, stream, state) {
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == ")") return popContext(state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") wordAsValue(stream);
return "parens";
};
states.pseudo = function(type, stream, state) {
if (type == "meta") return "pseudo";
if (type == "word") {
override = "variable-3";
return state.context.type;
}
return pass(type, stream, state);
};
states.documentTypes = function(type, stream, state) {
if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
override = "tag";
return state.context.type;
} else {
return states.atBlock(type, stream, state);
}
};
states.atBlock = function(type, stream, state) {
if (type == "(") return pushContext(state, stream, "atBlock_parens");
if (type == "}" || type == ";") return popAndPass(type, stream, state);
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") {
var word = stream.current().toLowerCase();
if (word == "only" || word == "not" || word == "and" || word == "or")
override = "keyword";
else if (mediaTypes.hasOwnProperty(word))
override = "attribute";
else if (mediaFeatures.hasOwnProperty(word))
override = "property";
else if (mediaValueKeywords.hasOwnProperty(word))
override = "keyword";
else if (propertyKeywords.hasOwnProperty(word))
override = "property";
else if (nonStandardPropertyKeywords.hasOwnProperty(word))
override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
else if (valueKeywords.hasOwnProperty(word))
override = "atom";
else if (colorKeywords.hasOwnProperty(word))
override = "keyword";
else
override = "error";
}
return state.context.type;
};
states.atComponentBlock = function(type, stream, state) {
if (type == "}")
return popAndPass(type, stream, state);
if (type == "{")
return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
if (type == "word")
override = "error";
return state.context.type;
};
states.atBlock_parens = function(type, stream, state) {
if (type == ")") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
return states.atBlock(type, stream, state);
};
states.restricted_atBlock_before = function(type, stream, state) {
if (type == "{")
return pushContext(state, stream, "restricted_atBlock");
if (type == "word" && state.stateArg == "@counter-style") {
override = "variable";
return "restricted_atBlock_before";
}
return pass(type, stream, state);
};
states.restricted_atBlock = function(type, stream, state) {
if (type == "}") {
state.stateArg = null;
return popContext(state);
}
if (type == "word") {
if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
(state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
override = "error";
else
override = "property";
return "maybeprop";
}
return "restricted_atBlock";
};
states.keyframes = function(type, stream, state) {
if (type == "word") { override = "variable"; return "keyframes"; }
if (type == "{") return pushContext(state, stream, "top");
return pass(type, stream, state);
};
states.at = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == "word") override = "tag";
else if (type == "hash") override = "builtin";
return "at";
};
states.interpolation = function(type, stream, state) {
if (type == "}") return popContext(state);
if (type == "{" || type == ";") return popAndPass(type, stream, state);
if (type == "word") override = "variable";
else if (type != "variable" && type != "(" && type != ")") override = "error";
return "interpolation";
};
return {
startState: function(base) {
return {tokenize: null,
state: inline ? "block" : "top",
stateArg: null,
context: new Context(inline ? "block" : "top", base || 0, null)};
},
token: function(stream, state) {
if (!state.tokenize && stream.eatSpace()) return null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style && typeof style == "object") {
type = style[1];
style = style[0];
}
override = style;
if (type != "comment")
state.state = states[state.state](type, stream, state);
return override;
},
indent: function(state, textAfter) {
var cx = state.context, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
if (cx.prev) {
if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
// Resume indentation from parent context.
cx = cx.prev;
indent = cx.indent;
} else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
// Dedent relative to current context.
indent = Math.max(0, cx.indent - indentUnit);
}
}
return indent;
},
electricChars: "}",
blockCommentStart: "/*",
blockCommentEnd: "*/",
blockCommentContinue: " * ",
lineComment: lineComment,
fold: "brace"
};
});
function keySet(array) {
var keys = {};
for (var i = 0; i < array.length; ++i) {
keys[array[i].toLowerCase()] = true;
}
return keys;
}
var documentTypes_ = [
"domain", "regexp", "url", "url-prefix"
], documentTypes = keySet(documentTypes_);
var mediaTypes_ = [
"all", "aural", "braille", "handheld", "print", "projection", "screen",
"tty", "tv", "embossed"
], mediaTypes = keySet(mediaTypes_);
var mediaFeatures_ = [
"width", "min-width", "max-width", "height", "min-height", "max-height",
"device-width", "min-device-width", "max-device-width", "device-height",
"min-device-height", "max-device-height", "aspect-ratio",
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
"max-color", "color-index", "min-color-index", "max-color-index",
"monochrome", "min-monochrome", "max-monochrome", "resolution",
"min-resolution", "max-resolution", "scan", "grid", "orientation",
"device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
"pointer", "any-pointer", "hover", "any-hover", "prefers-color-scheme",
"dynamic-range", "video-dynamic-range"
], mediaFeatures = keySet(mediaFeatures_);
var mediaValueKeywords_ = [
"landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
"interlace", "progressive",
"dark", "light",
"standard", "high"
], mediaValueKeywords = keySet(mediaValueKeywords_);
var propertyKeywords_ = [
"align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "all", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-fill-mode",
"animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "appearance", "azimuth", "backdrop-filter",
"backface-visibility", "background", "background-attachment",
"background-blend-mode", "background-clip", "background-color",
"background-image", "background-origin", "background-position",
"background-position-x", "background-position-y", "background-repeat",
"background-size", "baseline-shift", "binding", "bleed", "block-size",
"bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target",
"border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
"border-bottom-right-radius", "border-bottom-style", "border-bottom-width",
"border-collapse", "border-color", "border-image", "border-image-outset",
"border-image-repeat", "border-image-slice", "border-image-source",
"border-image-width", "border-left", "border-left-color", "border-left-style",
"border-left-width", "border-radius", "border-right", "border-right-color",
"border-right-style", "border-right-width", "border-spacing", "border-style",
"border-top", "border-top-color", "border-top-left-radius",
"border-top-right-radius", "border-top-style", "border-top-width",
"border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing",
"break-after", "break-before", "break-inside", "caption-side", "caret-color",
"clear", "clip", "color", "color-profile", "column-count", "column-fill",
"column-gap", "column-rule", "column-rule-color", "column-rule-style",
"column-rule-width", "column-span", "column-width", "columns", "contain",
"content", "counter-increment", "counter-reset", "crop", "cue", "cue-after",
"cue-before", "cursor", "direction", "display", "dominant-baseline",
"drop-initial-after-adjust", "drop-initial-after-align",
"drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size",
"drop-initial-value", "elevation", "empty-cells", "fit", "fit-content", "fit-position",
"flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow",
"flex-shrink", "flex-wrap", "float", "float-offset", "flow-from", "flow-into",
"font", "font-family", "font-feature-settings", "font-kerning",
"font-language-override", "font-optical-sizing", "font-size",
"font-size-adjust", "font-stretch", "font-style", "font-synthesis",
"font-variant", "font-variant-alternates", "font-variant-caps",
"font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric",
"font-variant-position", "font-variation-settings", "font-weight", "gap",
"grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows",
"grid-column", "grid-column-end", "grid-column-gap", "grid-column-start",
"grid-gap", "grid-row", "grid-row-end", "grid-row-gap", "grid-row-start",
"grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon",
"image-orientation", "image-rendering", "image-resolution", "inline-box-align",
"inset", "inset-block", "inset-block-end", "inset-block-start", "inset-inline",
"inset-inline-end", "inset-inline-start", "isolation", "justify-content",
"justify-items", "justify-self", "left", "letter-spacing", "line-break",
"line-height", "line-height-step", "line-stacking", "line-stacking-ruby",
"line-stacking-shift", "line-stacking-strategy", "list-style",
"list-style-image", "list-style-position", "list-style-type", "margin",
"margin-bottom", "margin-left", "margin-right", "margin-top", "marks",
"marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed",
"marquee-style", "mask-clip", "mask-composite", "mask-image", "mask-mode",
"mask-origin", "mask-position", "mask-repeat", "mask-size","mask-type",
"max-block-size", "max-height", "max-inline-size",
"max-width", "min-block-size", "min-height", "min-inline-size", "min-width",
"mix-blend-mode", "move-to", "nav-down", "nav-index", "nav-left", "nav-right",
"nav-up", "object-fit", "object-position", "offset", "offset-anchor",
"offset-distance", "offset-path", "offset-position", "offset-rotate",
"opacity", "order", "orphans", "outline", "outline-color", "outline-offset",
"outline-style", "outline-width", "overflow", "overflow-style",
"overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom",
"padding-left", "padding-right", "padding-top", "page", "page-break-after",
"page-break-before", "page-break-inside", "page-policy", "pause",
"pause-after", "pause-before", "perspective", "perspective-origin", "pitch",
"pitch-range", "place-content", "place-items", "place-self", "play-during",
"position", "presentation-level", "punctuation-trim", "quotes",
"region-break-after", "region-break-before", "region-break-inside",
"region-fragment", "rendering-intent", "resize", "rest", "rest-after",
"rest-before", "richness", "right", "rotate", "rotation", "rotation-point",
"row-gap", "ruby-align", "ruby-overhang", "ruby-position", "ruby-span",
"scale", "scroll-behavior", "scroll-margin", "scroll-margin-block",
"scroll-margin-block-end", "scroll-margin-block-start", "scroll-margin-bottom",
"scroll-margin-inline", "scroll-margin-inline-end",
"scroll-margin-inline-start", "scroll-margin-left", "scroll-margin-right",
"scroll-margin-top", "scroll-padding", "scroll-padding-block",
"scroll-padding-block-end", "scroll-padding-block-start",
"scroll-padding-bottom", "scroll-padding-inline", "scroll-padding-inline-end",
"scroll-padding-inline-start", "scroll-padding-left", "scroll-padding-right",
"scroll-padding-top", "scroll-snap-align", "scroll-snap-type",
"shape-image-threshold", "shape-inside", "shape-margin", "shape-outside",
"size", "speak", "speak-as", "speak-header", "speak-numeral",
"speak-punctuation", "speech-rate", "stress", "string-set", "tab-size",
"table-layout", "target", "target-name", "target-new", "target-position",
"text-align", "text-align-last", "text-combine-upright", "text-decoration",
"text-decoration-color", "text-decoration-line", "text-decoration-skip",
"text-decoration-skip-ink", "text-decoration-style", "text-emphasis",
"text-emphasis-color", "text-emphasis-position", "text-emphasis-style",
"text-height", "text-indent", "text-justify", "text-orientation",
"text-outline", "text-overflow", "text-rendering", "text-shadow",
"text-size-adjust", "text-space-collapse", "text-transform",
"text-underline-position", "text-wrap", "top", "touch-action", "transform", "transform-origin",
"transform-style", "transition", "transition-delay", "transition-duration",
"transition-property", "transition-timing-function", "translate",
"unicode-bidi", "user-select", "vertical-align", "visibility", "voice-balance",
"voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate",
"voice-stress", "voice-volume", "volume", "white-space", "widows", "width",
"will-change", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index",
// SVG-specific
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
"color-interpolation", "color-interpolation-filters",
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
"marker", "marker-end", "marker-mid", "marker-start", "paint-order", "shape-rendering", "stroke",
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
"glyph-orientation-vertical", "text-anchor", "writing-mode",
], propertyKeywords = keySet(propertyKeywords_);
var nonStandardPropertyKeywords_ = [
"accent-color", "aspect-ratio", "border-block", "border-block-color", "border-block-end",
"border-block-end-color", "border-block-end-style", "border-block-end-width",
"border-block-start", "border-block-start-color", "border-block-start-style",
"border-block-start-width", "border-block-style", "border-block-width",
"border-inline", "border-inline-color", "border-inline-end",
"border-inline-end-color", "border-inline-end-style",
"border-inline-end-width", "border-inline-start", "border-inline-start-color",
"border-inline-start-style", "border-inline-start-width",
"border-inline-style", "border-inline-width", "content-visibility", "margin-block",
"margin-block-end", "margin-block-start", "margin-inline", "margin-inline-end",
"margin-inline-start", "overflow-anchor", "overscroll-behavior", "padding-block", "padding-block-end",
"padding-block-start", "padding-inline", "padding-inline-end",
"padding-inline-start", "scroll-snap-stop", "scrollbar-3d-light-color",
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
"scrollbar-track-color", "searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration", "shape-inside", "zoom"
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
var fontProperties_ = [
"font-display", "font-family", "src", "unicode-range", "font-variant",
"font-feature-settings", "font-stretch", "font-weight", "font-style"
], fontProperties = keySet(fontProperties_);
var counterDescriptors_ = [
"additive-symbols", "fallback", "negative", "pad", "prefix", "range",
"speak-as", "suffix", "symbols", "system"
], counterDescriptors = keySet(counterDescriptors_);
var colorKeywords_ = [
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
"darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick",
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen"
], colorKeywords = keySet(colorKeywords_);
var valueKeywords_ = [
"above", "absolute", "activeborder", "additive", "activecaption", "afar",
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
"arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
"avoid-region", "axis-pan", "background", "backwards", "baseline", "below", "bidi-override", "binary",
"bengali", "blink", "block", "block-axis", "blur", "bold", "bolder", "border", "border-box",
"both", "bottom", "break", "break-all", "break-word", "brightness", "bullets", "button",
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
"compact", "condensed", "conic-gradient", "contain", "content", "contents",
"content-box", "context-menu", "continuous", "contrast", "copy", "counter", "counters", "cover", "crop",
"cross", "crosshair", "cubic-bezier", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
"destination-in", "destination-out", "destination-over", "devanagari", "difference",
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
"dot-dash", "dot-dot-dash",
"dotted", "double", "down", "drop-shadow", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fill-box", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "grayscale", "graytext", "grid", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "hue-rotate", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "japanese-formal", "japanese-informal", "justify", "kannada",
"katakana", "katakana-iroha", "keep-all", "khmer",
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
"landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
"line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "manipulation", "match", "matrix", "matrix3d",
"media-play-button", "media-slider", "media-sliderthumb",
"media-volume-slider", "media-volume-sliderthumb", "medium",
"menu", "menulist", "menulist-button",
"menutext", "message-box", "middle", "min-intrinsic",
"mix", "mongolian", "monospace", "move", "multiple", "multiple_mask_images", "multiply", "myanmar", "n-resize",
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
"painted", "page", "paused", "persian", "perspective", "pinch-zoom", "plus-darker", "plus-lighter",
"pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
"progress", "push-button", "radial-gradient", "radio", "read-only",
"read-write", "read-write-plaintext-only", "rectangle", "region",
"relative", "repeat", "repeating-linear-gradient", "repeating-radial-gradient",
"repeating-conic-gradient", "repeat-x", "repeat-y", "reset", "reverse",
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
"s-resize", "sans-serif", "saturate", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
"scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
"searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
"semi-condensed", "semi-expanded", "separate", "sepia", "serif", "show", "sidama",
"simp-chinese-formal", "simp-chinese-informal", "single",
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
"small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
"source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
"square-button", "start", "static", "status-bar", "stretch", "stroke", "stroke-box", "sub",
"subpixel-antialiased", "svg_masks", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
"table-caption", "table-cell", "table-column", "table-column-group",
"table-footer-group", "table-header-group", "table-row", "table-row-group",
"tamil",
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
"trad-chinese-formal", "trad-chinese-informal", "transform",
"translate", "translate3d", "translateX", "translateY", "translateZ",
"transparent", "ultra-condensed", "ultra-expanded", "underline", "unidirectional-pan", "unset", "up",
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
"var", "vertical", "vertical-text", "view-box", "visible", "visibleFill", "visiblePainted",
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
"window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
"xx-large", "xx-small"
], valueKeywords = keySet(valueKeywords_);
var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
.concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
.concat(valueKeywords_);
CodeMirror.registerHelper("hintWords", "css", allWords);
function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch == "/") {
state.tokenize = null;
break;
}
maybeEnd = (ch == "*");
}
return ["comment", "comment"];
}
CodeMirror.defineMIME("text/css", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css"
});
CodeMirror.defineMIME("text/x-scss", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
lineComment: "//",
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
":": function(stream) {
if (stream.match(/^\s*\{/, false))
return [null, null]
return false;
},
"$": function(stream) {
stream.match(/^[\w-]+/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"#": function(stream) {
if (!stream.eat("{")) return false;
return [null, "interpolation"];
}
},
name: "css",
helperType: "scss"
});
CodeMirror.defineMIME("text/x-less", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
mediaValueKeywords: mediaValueKeywords,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
lineComment: "//",
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
"@": function(stream) {
if (stream.eat("{")) return [null, "interpolation"];
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false;
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"&": function() {
return ["atom", "atom"];
}
},
name: "css",
helperType: "less"
});
CodeMirror.defineMIME("text/x-gss", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
supportsAtComponent: true,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css",
helperType: "gss"
});
});
@@ -0,0 +1,153 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var defaultTags = {
script: [
["lang", /(javascript|babel)/i, "javascript"],
["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
["type", /./, "text/plain"],
[null, null, "javascript"]
],
style: [
["lang", /^css$/i, "css"],
["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
["type", /./, "text/plain"],
[null, null, "css"]
]
};
function maybeBackup(stream, pat, style) {
var cur = stream.current(), close = cur.search(pat);
if (close > -1) {
stream.backUp(cur.length - close);
} else if (cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur);
}
return style;
}
var attrRegexpCache = {};
function getAttrRegexp(attr) {
var regexp = attrRegexpCache[attr];
if (regexp) return regexp;
return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
}
function getAttrValue(text, attr) {
var match = text.match(getAttrRegexp(attr))
return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
}
function getTagRegexp(tagName, anchored) {
return new RegExp((anchored ? "^" : "") + "<\/\\s*" + tagName + "\\s*>", "i");
}
function addTags(from, to) {
for (var tag in from) {
var dest = to[tag] || (to[tag] = []);
var source = from[tag];
for (var i = source.length - 1; i >= 0; i--)
dest.unshift(source[i])
}
}
function findMatchingMode(tagInfo, tagText) {
for (var i = 0; i < tagInfo.length; i++) {
var spec = tagInfo[i];
if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];
}
}
CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {
name: "xml",
htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag,
allowMissingTagName: parserConfig.allowMissingTagName,
});
var tags = {};
var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
addTags(defaultTags, tags);
if (configTags) addTags(configTags, tags);
if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
function html(stream, state) {
var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName
if (tag && !/[<>\s\/]/.test(stream.current()) &&
(tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&
tags.hasOwnProperty(tagName)) {
state.inTag = tagName + " "
} else if (state.inTag && tag && />$/.test(stream.current())) {
var inTag = /^([\S]+) (.*)/.exec(state.inTag)
state.inTag = null
var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2])
var mode = CodeMirror.getMode(config, modeSpec)
var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);
state.token = function (stream, state) {
if (stream.match(endTagA, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;
}
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
};
state.localMode = mode;
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", ""));
} else if (state.inTag) {
state.inTag += stream.current()
if (stream.eol()) state.inTag += " "
}
return style;
};
return {
startState: function () {
var state = CodeMirror.startState(htmlMode);
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
},
copyState: function (state) {
var local;
if (state.localState) {
local = CodeMirror.copyState(state.localMode, state.localState);
}
return {token: state.token, inTag: state.inTag,
localMode: state.localMode, localState: local,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},
token: function (stream, state) {
return state.token(stream, state);
},
indent: function (state, textAfter, line) {
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter, line);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter, line);
else
return CodeMirror.Pass;
},
innerMode: function (state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
};
}, "xml", "javascript", "css");
CodeMirror.defineMIME("text/html", "htmlmixed");
});
@@ -0,0 +1,960 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var trackScope = parserConfig.trackScope !== false
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C
};
}();
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eat("=");
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#" && stream.peek() == "!") {
stream.skipToEnd();
return ret("meta", "meta");
} else if (ch == "#" && stream.eatWhile(wordRE)) {
return ret("variable", "property")
} else if (ch == "<" && stream.match("!--") ||
(ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
stream.skipToEnd()
return ret("comment", "comment")
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {
if (ch == "!" || ch == "=") stream.eat("=")
} else if (/[<>*+\-|&?]/.test(ch)) {
stream.eat(ch)
if (ch == ">") stream.eat(ch)
}
}
if (ch == "?" && stream.eat(".")) return ret(".")
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current()
if (state.lastType != ".") {
if (keywords.propertyIsEnumerable(word)) {
var kw = keywords[word]
return ret(kw.type, kw.style, word)
}
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
return ret("async", "keyword", word)
}
return ret("variable", "variable", word)
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/`]/.test(ch)) {
for (;; --pos) {
if (pos == 0) return
var next = stream.string.charAt(pos - 1)
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
}
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
"regexp": true, "this": true, "import": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
if (!trackScope) return false
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function inList(name, list) {
for (var v = list; v; v = v.next) if (v.name == name) return true
return false;
}
function register(varname) {
var state = cx.state;
cx.marked = "def";
if (!trackScope) return
if (state.context) {
if (state.lexical.info == "var" && state.context && state.context.block) {
// FIXME function decls are also not block scoped
var newContext = registerVarScoped(varname, state.context)
if (newContext != null) {
state.context = newContext
return
}
} else if (!inList(varname, state.localVars)) {
state.localVars = new Var(varname, state.localVars)
return
}
}
// Fall through means this is global
if (parserConfig.globalVars && !inList(varname, state.globalVars))
state.globalVars = new Var(varname, state.globalVars)
}
function registerVarScoped(varname, context) {
if (!context) {
return null
} else if (context.block) {
var inner = registerVarScoped(varname, context.prev)
if (!inner) return null
if (inner == context.prev) return context
return new Context(inner, context.vars, true)
} else if (inList(varname, context.vars)) {
return context
} else {
return new Context(context.prev, new Var(varname, context.vars), false)
}
}
function isModifier(name) {
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
}
// Combinators
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
function Var(name, next) { this.name = name; this.next = next }
var defaultVars = new Var("this", new Var("arguments", null))
function pushcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
cx.state.localVars = defaultVars
}
function pushblockcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
cx.state.localVars = null
}
pushcontext.lex = pushblockcontext.lex = true
function popcontext() {
cx.state.localVars = cx.state.context.vars
cx.state.context = cx.state.context.prev
}
popcontext.lex = true
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
if (type == "debugger") return cont(expect(";"));
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
if (type == "class" || (isTS && value == "interface")) {
cx.marked = "keyword"
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
if (isTS && value == "declare") {
cx.marked = "keyword"
return cont(statement)
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
if (value == "enum") return cont(enumdef);
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, statement, poplex)
} else if (isTS && value == "abstract") {
cx.marked = "keyword"
return cont(statement)
} else {
return cont(pushlex("stat"), maybelabel);
}
}
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
block, poplex, poplex, popcontext);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function maybeCatchBinding(type) {
if (type == "(") return cont(funarg, expect(")"))
}
function expression(type, value) {
return expressionInner(type, value, false);
}
function expressionNoComma(type, value) {
return expressionInner(type, value, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
}
function expressionInner(type, value, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") return pass(quasi, maybeop);
if (type == "new") return cont(maybeTarget(noComma));
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(maybeexpression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
if (type == "regexp") {
cx.state.lastType = cx.marked = "operator"
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
return cont(expr)
}
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(maybeexpression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybeTarget(noComma) {
return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target);
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
else return pass(noComma ? expressionNoComma : expression);
};
}
function target(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
}
function targetNoComma(_, value) {
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
cx.state.fatArrowAt = cx.stream.pos + m[0].length
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (isTS && isModifier(value)) {
cx.marked = "keyword"
return cont(objprop)
} else if (type == "[") {
return cont(expression, maybetype, expect("]"), afterprop);
} else if (type == "spread") {
return cont(expressionNoComma, afterprop);
} else if (value == "*") {
cx.marked = "keyword";
return cont(objprop);
} else if (type == ":") {
return pass(afterprop)
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end, sep) {
function proceed(type, value) {
if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
}
if (type == end || value == end) return cont();
if (sep && sep.indexOf(";") > -1) return pass(what)
return cont(expect(end));
}
return function(type, value) {
if (type == end || value == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type, value) {
if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
}
function maybetypeOrIn(type, value) {
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
}
function mayberettype(type) {
if (isTS && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
else return cont(typeexpr)
}
}
function isKW(_, value) {
if (value == "is") {
cx.marked = "keyword"
return cont()
}
}
function typeexpr(type, value) {
if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
cx.marked = "keyword"
return cont(value == "typeof" ? expressionNoComma : typeexpr)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
return cont(afterType)
}
if (value == "|" || value == "&") return cont(typeexpr)
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
if (type == "quasi") { return pass(quasiType, afterType); }
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprops(type) {
if (type.match(/[\}\)\]]/)) return cont()
if (type == "," || type == ";") return cont(typeprops)
return pass(typeprop, typeprops)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
} else if (type == "(") {
return pass(functiondecl, typeprop)
} else if (!type.match(/[;\}\)\],]/)) {
return cont()
}
}
function quasiType(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasiType);
return cont(typeexpr, continueQuasiType);
}
function continueQuasiType(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasiType);
}
}
function typearg(type, value) {
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
if (type == ":") return cont(typeexpr)
if (type == "spread") return cont(typearg)
return pass(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
if (type == "[") return cont(typeexpr, expect("]"), afterType)
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
}
function maybeTypeArgs(_, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
}
function typeparam() {
return pass(typeexpr, maybeTypeDefault)
}
function maybeTypeDefault(_, value) {
if (value == "=") return cont(typeexpr)
}
function vardef(_, value) {
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(eltpattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern);
if (type == "}") return pass();
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
return cont(expect(":"), pattern, maybeAssign);
}
function eltpattern() {
return pass(pattern, maybeAssign)
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type, value) {
if (value == "await") return cont(forspec);
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, forspec2);
if (type == "variable") return cont(forspec2);
return pass(forspec2)
}
function forspec2(type, value) {
if (type == ")") return cont()
if (type == ";") return cont(forspec2)
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
return pass(expression, forspec2)
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
}
function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
cx.marked = "type"
return cont(typename)
} else if (value == "<") {
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
}
}
function funarg(type, value) {
if (value == "@") cont(expression, funarg)
if (type == "spread") return cont(funarg);
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
if (isTS && type == "this") return cont(maybetype, maybeAssign)
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
if (value == "implements") cx.marked = "keyword";
return cont(isTS ? typeexpr : expression, classNameAfter);
}
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "async" ||
(type == "variable" &&
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
cx.marked = "keyword";
return cont(classBody);
}
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
return cont(classfield, classBody);
}
if (type == "number" || type == "string") return cont(classfield, classBody);
if (type == "[")
return cont(expression, maybetype, expect("]"), classfield, classBody)
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (isTS && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
}
function classfield(type, value) {
if (value == "!") return cont(classfield)
if (value == "?") return cont(classfield)
if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
return pass(isInterface ? functiondecl : functiondef)
}
function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement);
}
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) {
if (type == "string") return cont();
if (type == "(") return pass(expression);
if (type == ".") return pass(maybeoperatorComma);
return pass(importSpec, maybeMoreImports, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(commasep(expressionNoComma, "]"));
}
function enumdef() {
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
}
function enummember() {
return pass(pattern, maybeAssign);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
function expressionAllowed(stream, state, backUp) {
return state.tokenize == tokenBase &&
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && new Context(null, null, false),
indented: basecolumn || 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse && c != popcontext) break;
}
while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
blockCommentContinue: jsonMode ? null : " * ",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode,
expressionAllowed: expressionAllowed,
skipExpression: function(state) {
parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
}
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});
@@ -0,0 +1,886 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../meta"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var htmlMode = CodeMirror.getMode(cmCfg, "text/html");
var htmlModeMissing = htmlMode.name == "null"
function getMode(name) {
if (CodeMirror.findModeByName) {
var found = CodeMirror.findModeByName(name);
if (found) name = found.mime || found.mimes[0];
}
var mode = CodeMirror.getMode(cmCfg, name);
return mode.name == "null" ? null : mode;
}
// Should characters that affect highlighting be highlighted separate?
// Does not include characters that will be output (such as `1.` and `-` for lists)
if (modeCfg.highlightFormatting === undefined)
modeCfg.highlightFormatting = false;
// Maximum number of nested blockquotes. Set to 0 for infinite nesting.
// Excess `>` will emit `error` token.
if (modeCfg.maxBlockquoteDepth === undefined)
modeCfg.maxBlockquoteDepth = 0;
// Turn on task lists? ("- [ ] " and "- [x] ")
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
// Turn on strikethrough syntax
if (modeCfg.strikethrough === undefined)
modeCfg.strikethrough = false;
if (modeCfg.emoji === undefined)
modeCfg.emoji = false;
if (modeCfg.fencedCodeBlockHighlighting === undefined)
modeCfg.fencedCodeBlockHighlighting = true;
if (modeCfg.fencedCodeBlockDefaultMode === undefined)
modeCfg.fencedCodeBlockDefaultMode = 'text/plain';
if (modeCfg.xml === undefined)
modeCfg.xml = true;
// Allow token types to be overridden by user-provided token types.
if (modeCfg.tokenTypeOverrides === undefined)
modeCfg.tokenTypeOverrides = {};
var tokenTypes = {
header: "header",
code: "comment",
quote: "quote",
list1: "variable-2",
list2: "variable-3",
list3: "keyword",
hr: "hr",
image: "image",
imageAltText: "image-alt-text",
imageMarker: "image-marker",
formatting: "formatting",
linkInline: "link",
linkEmail: "link",
linkText: "link",
linkHref: "string",
em: "em",
strong: "strong",
strikethrough: "strikethrough",
emoji: "builtin"
};
for (var tokenType in tokenTypes) {
if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) {
tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType];
}
}
var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/
, listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/
, taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
, setextHeaderRE = /^ {0,3}(?:\={1,}|-{2,})\s*$/
, textRE = /^[^#!\[\]*_\\<>` "'(~:]+/
, fencedCodeRE = /^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/
, linkDefRE = /^\s*\[[^\]]+?\]:.*$/ // naive link-definition
, punctuation = /[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/
, expandedTab = " " // CommonMark specifies tab as 4 spaces
function switchInline(stream, state, f) {
state.f = state.inline = f;
return f(stream, state);
}
function switchBlock(stream, state, f) {
state.f = state.block = f;
return f(stream, state);
}
function lineIsEmpty(line) {
return !line || !/\S/.test(line.string)
}
// Blocks
function blankLine(state) {
// Reset linkTitle state
state.linkTitle = false;
state.linkHref = false;
state.linkText = false;
// Reset EM state
state.em = false;
// Reset STRONG state
state.strong = false;
// Reset strikethrough state
state.strikethrough = false;
// Reset state.quote
state.quote = 0;
// Reset state.indentedCode
state.indentedCode = false;
if (state.f == htmlBlock) {
var exit = htmlModeMissing
if (!exit) {
var inner = CodeMirror.innerMode(htmlMode, state.htmlState)
exit = inner.mode.name == "xml" && inner.state.tagStart === null &&
(!inner.state.context && inner.state.tokenize.isInText)
}
if (exit) {
state.f = inlineNormal;
state.block = blockNormal;
state.htmlState = null;
}
}
// Reset state.trailingSpace
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
// Mark this line as blank
state.prevLine = state.thisLine
state.thisLine = {stream: null}
return null;
}
function blockNormal(stream, state) {
var firstTokenOnLine = stream.column() === state.indentation;
var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream);
var prevLineIsIndentedCode = state.indentedCode;
var prevLineIsHr = state.prevLine.hr;
var prevLineIsList = state.list !== false;
var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3;
state.indentedCode = false;
var lineIndentation = state.indentation;
// compute once per line (on first token)
if (state.indentationDiff === null) {
state.indentationDiff = state.indentation;
if (prevLineIsList) {
state.list = null;
// While this list item's marker's indentation is less than the deepest
// list item's content's indentation,pop the deepest list item
// indentation off the stack, and update block indentation state
while (lineIndentation < state.listStack[state.listStack.length - 1]) {
state.listStack.pop();
if (state.listStack.length) {
state.indentation = state.listStack[state.listStack.length - 1];
// less than the first list's indent -> the line is no longer a list
} else {
state.list = false;
}
}
if (state.list !== false) {
state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1]
}
}
}
// not comprehensive (currently only for setext detection purposes)
var allowsInlineContinuation = (
!prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header &&
(!prevLineIsList || !prevLineIsIndentedCode) &&
!state.prevLine.fencedCodeEnd
);
var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) &&
state.indentation <= maxNonCodeIndentation && stream.match(hrRE);
var match = null;
if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd ||
state.prevLine.header || prevLineLineIsEmpty)) {
stream.skipToEnd();
state.indentedCode = true;
return tokenTypes.code;
} else if (stream.eatSpace()) {
return null;
} else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) {
state.quote = 0;
state.header = match[1].length;
state.thisLine.header = true;
if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline;
return getType(state);
} else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) {
state.quote = firstTokenOnLine ? 1 : state.quote + 1;
if (modeCfg.highlightFormatting) state.formatting = "quote";
stream.eatSpace();
return getType(state);
} else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) {
var listType = match[1] ? "ol" : "ul";
state.indentation = lineIndentation + stream.current().length;
state.list = true;
state.quote = 0;
// Add this list item's content's indentation to the stack
state.listStack.push(state.indentation);
// Reset inline styles which shouldn't propagate across list items
state.em = false;
state.strong = false;
state.code = false;
state.strikethrough = false;
if (modeCfg.taskLists && stream.match(taskListRE, false)) {
state.taskList = true;
}
state.f = state.inline;
if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
return getType(state);
} else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) {
state.quote = 0;
state.fencedEndRE = new RegExp(match[1] + "+ *$");
// try switching mode
state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2] || modeCfg.fencedCodeBlockDefaultMode );
if (state.localMode) state.localState = CodeMirror.startState(state.localMode);
state.f = state.block = local;
if (modeCfg.highlightFormatting) state.formatting = "code-block";
state.code = -1
return getType(state);
// SETEXT has lowest block-scope precedence after HR, so check it after
// the others (code, blockquote, list...)
} else if (
// if setext set, indicates line after ---/===
state.setext || (
// line before ---/===
(!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false &&
!state.code && !isHr && !linkDefRE.test(stream.string) &&
(match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE))
)
) {
if ( !state.setext ) {
state.header = match[0].charAt(0) == '=' ? 1 : 2;
state.setext = state.header;
} else {
state.header = state.setext;
// has no effect on type so we can reset it now
state.setext = 0;
stream.skipToEnd();
if (modeCfg.highlightFormatting) state.formatting = "header";
}
state.thisLine.header = true;
state.f = state.inline;
return getType(state);
} else if (isHr) {
stream.skipToEnd();
state.hr = true;
state.thisLine.hr = true;
return tokenTypes.hr;
} else if (stream.peek() === '[') {
return switchInline(stream, state, footnoteLink);
}
return switchInline(stream, state, state.inline);
}
function htmlBlock(stream, state) {
var style = htmlMode.token(stream, state.htmlState);
if (!htmlModeMissing) {
var inner = CodeMirror.innerMode(htmlMode, state.htmlState)
if ((inner.mode.name == "xml" && inner.state.tagStart === null &&
(!inner.state.context && inner.state.tokenize.isInText)) ||
(state.md_inside && stream.current().indexOf(">") > -1)) {
state.f = inlineNormal;
state.block = blockNormal;
state.htmlState = null;
}
}
return style;
}
function local(stream, state) {
var currListInd = state.listStack[state.listStack.length - 1] || 0;
var hasExitedList = state.indentation < currListInd;
var maxFencedEndInd = currListInd + 3;
if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) {
if (modeCfg.highlightFormatting) state.formatting = "code-block";
var returnType;
if (!hasExitedList) returnType = getType(state)
state.localMode = state.localState = null;
state.block = blockNormal;
state.f = inlineNormal;
state.fencedEndRE = null;
state.code = 0
state.thisLine.fencedCodeEnd = true;
if (hasExitedList) return switchBlock(stream, state, state.block);
return returnType;
} else if (state.localMode) {
return state.localMode.token(stream, state.localState);
} else {
stream.skipToEnd();
return tokenTypes.code;
}
}
// Inline
function getType(state) {
var styles = [];
if (state.formatting) {
styles.push(tokenTypes.formatting);
if (typeof state.formatting === "string") state.formatting = [state.formatting];
for (var i = 0; i < state.formatting.length; i++) {
styles.push(tokenTypes.formatting + "-" + state.formatting[i]);
if (state.formatting[i] === "header") {
styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header);
}
// Add `formatting-quote` and `formatting-quote-#` for blockquotes
// Add `error` instead if the maximum blockquote nesting depth is passed
if (state.formatting[i] === "quote") {
if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote);
} else {
styles.push("error");
}
}
}
}
if (state.taskOpen) {
styles.push("meta");
return styles.length ? styles.join(' ') : null;
}
if (state.taskClosed) {
styles.push("property");
return styles.length ? styles.join(' ') : null;
}
if (state.linkHref) {
styles.push(tokenTypes.linkHref, "url");
} else { // Only apply inline styles to non-url text
if (state.strong) { styles.push(tokenTypes.strong); }
if (state.em) { styles.push(tokenTypes.em); }
if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }
if (state.emoji) { styles.push(tokenTypes.emoji); }
if (state.linkText) { styles.push(tokenTypes.linkText); }
if (state.code) { styles.push(tokenTypes.code); }
if (state.image) { styles.push(tokenTypes.image); }
if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); }
if (state.imageMarker) { styles.push(tokenTypes.imageMarker); }
}
if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); }
if (state.quote) {
styles.push(tokenTypes.quote);
// Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
styles.push(tokenTypes.quote + "-" + state.quote);
} else {
styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth);
}
}
if (state.list !== false) {
var listMod = (state.listStack.length - 1) % 3;
if (!listMod) {
styles.push(tokenTypes.list1);
} else if (listMod === 1) {
styles.push(tokenTypes.list2);
} else {
styles.push(tokenTypes.list3);
}
}
if (state.trailingSpaceNewLine) {
styles.push("trailing-space-new-line");
} else if (state.trailingSpace) {
styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
}
return styles.length ? styles.join(' ') : null;
}
function handleText(stream, state) {
if (stream.match(textRE, true)) {
return getType(state);
}
return undefined;
}
function inlineNormal(stream, state) {
var style = state.text(stream, state);
if (typeof style !== 'undefined')
return style;
if (state.list) { // List marker (*, +, -, 1., etc)
state.list = null;
return getType(state);
}
if (state.taskList) {
var taskOpen = stream.match(taskListRE, true)[1] === " ";
if (taskOpen) state.taskOpen = true;
else state.taskClosed = true;
if (modeCfg.highlightFormatting) state.formatting = "task";
state.taskList = false;
return getType(state);
}
state.taskOpen = false;
state.taskClosed = false;
if (state.header && stream.match(/^#+$/, true)) {
if (modeCfg.highlightFormatting) state.formatting = "header";
return getType(state);
}
var ch = stream.next();
// Matches link titles present on next line
if (state.linkTitle) {
state.linkTitle = false;
var matchCh = ch;
if (ch === '(') {
matchCh = ')';
}
matchCh = (matchCh+'').replace(/([.?*+^\[\]\\(){}|-])/g, "\\$1");
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
if (stream.match(new RegExp(regex), true)) {
return tokenTypes.linkHref;
}
}
// If this block is changed, it may need to be updated in GFM mode
if (ch === '`') {
var previousFormatting = state.formatting;
if (modeCfg.highlightFormatting) state.formatting = "code";
stream.eatWhile('`');
var count = stream.current().length
if (state.code == 0 && (!state.quote || count == 1)) {
state.code = count
return getType(state)
} else if (count == state.code) { // Must be exact
var t = getType(state)
state.code = 0
return t
} else {
state.formatting = previousFormatting
return getType(state)
}
} else if (state.code) {
return getType(state);
}
if (ch === '\\') {
stream.next();
if (modeCfg.highlightFormatting) {
var type = getType(state);
var formattingEscape = tokenTypes.formatting + "-escape";
return type ? type + " " + formattingEscape : formattingEscape;
}
}
if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
state.imageMarker = true;
state.image = true;
if (modeCfg.highlightFormatting) state.formatting = "image";
return getType(state);
}
if (ch === '[' && state.imageMarker && stream.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/, false)) {
state.imageMarker = false;
state.imageAltText = true
if (modeCfg.highlightFormatting) state.formatting = "image";
return getType(state);
}
if (ch === ']' && state.imageAltText) {
if (modeCfg.highlightFormatting) state.formatting = "image";
var type = getType(state);
state.imageAltText = false;
state.image = false;
state.inline = state.f = linkHref;
return type;
}
if (ch === '[' && !state.image) {
if (state.linkText && stream.match(/^.*?\]/)) return getType(state)
state.linkText = true;
if (modeCfg.highlightFormatting) state.formatting = "link";
return getType(state);
}
if (ch === ']' && state.linkText) {
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
state.linkText = false;
state.inline = state.f = stream.match(/\(.*?\)| ?\[.*?\]/, false) ? linkHref : inlineNormal
return type;
}
if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
state.f = state.inline = linkInline;
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
if (type){
type += " ";
} else {
type = "";
}
return type + tokenTypes.linkInline;
}
if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
state.f = state.inline = linkInline;
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
if (type){
type += " ";
} else {
type = "";
}
return type + tokenTypes.linkEmail;
}
if (modeCfg.xml && ch === '<' && stream.match(/^(!--|\?|!\[CDATA\[|[a-z][a-z0-9-]*(?:\s+[a-z_:.\-]+(?:\s*=\s*[^>]+)?)*\s*(?:>|$))/i, false)) {
var end = stream.string.indexOf(">", stream.pos);
if (end != -1) {
var atts = stream.string.substring(stream.start, end);
if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true;
}
stream.backUp(1);
state.htmlState = CodeMirror.startState(htmlMode);
return switchBlock(stream, state, htmlBlock);
}
if (modeCfg.xml && ch === '<' && stream.match(/^\/\w*?>/)) {
state.md_inside = false;
return "tag";
} else if (ch === "*" || ch === "_") {
var len = 1, before = stream.pos == 1 ? " " : stream.string.charAt(stream.pos - 2)
while (len < 3 && stream.eat(ch)) len++
var after = stream.peek() || " "
// See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis
var leftFlanking = !/\s/.test(after) && (!punctuation.test(after) || /\s/.test(before) || punctuation.test(before))
var rightFlanking = !/\s/.test(before) && (!punctuation.test(before) || /\s/.test(after) || punctuation.test(after))
var setEm = null, setStrong = null
if (len % 2) { // Em
if (!state.em && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before)))
setEm = true
else if (state.em == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after)))
setEm = false
}
if (len > 1) { // Strong
if (!state.strong && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before)))
setStrong = true
else if (state.strong == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after)))
setStrong = false
}
if (setStrong != null || setEm != null) {
if (modeCfg.highlightFormatting) state.formatting = setEm == null ? "strong" : setStrong == null ? "em" : "strong em"
if (setEm === true) state.em = ch
if (setStrong === true) state.strong = ch
var t = getType(state)
if (setEm === false) state.em = false
if (setStrong === false) state.strong = false
return t
}
} else if (ch === ' ') {
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state);
} else { // Not surrounded by spaces, back up pointer
stream.backUp(1);
}
}
}
if (modeCfg.strikethrough) {
if (ch === '~' && stream.eatWhile(ch)) {
if (state.strikethrough) {// Remove strikethrough
if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
var t = getType(state);
state.strikethrough = false;
return t;
} else if (stream.match(/^[^\s]/, false)) {// Add strikethrough
state.strikethrough = true;
if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
return getType(state);
}
} else if (ch === ' ') {
if (stream.match('~~', true)) { // Probably surrounded by space
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state);
} else { // Not surrounded by spaces, back up pointer
stream.backUp(2);
}
}
}
}
if (modeCfg.emoji && ch === ":" && stream.match(/^(?:[a-z_\d+][a-z_\d+-]*|\-[a-z_\d+][a-z_\d+-]*):/)) {
state.emoji = true;
if (modeCfg.highlightFormatting) state.formatting = "emoji";
var retType = getType(state);
state.emoji = false;
return retType;
}
if (ch === ' ') {
if (stream.match(/^ +$/, false)) {
state.trailingSpace++;
} else if (state.trailingSpace) {
state.trailingSpaceNewLine = true;
}
}
return getType(state);
}
function linkInline(stream, state) {
var ch = stream.next();
if (ch === ">") {
state.f = state.inline = inlineNormal;
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
if (type){
type += " ";
} else {
type = "";
}
return type + tokenTypes.linkInline;
}
stream.match(/^[^>]+/, true);
return tokenTypes.linkInline;
}
function linkHref(stream, state) {
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
var ch = stream.next();
if (ch === '(' || ch === '[') {
state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]");
if (modeCfg.highlightFormatting) state.formatting = "link-string";
state.linkHref = true;
return getType(state);
}
return 'error';
}
var linkRE = {
")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,
"]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/
}
function getLinkHrefInside(endChar) {
return function(stream, state) {
var ch = stream.next();
if (ch === endChar) {
state.f = state.inline = inlineNormal;
if (modeCfg.highlightFormatting) state.formatting = "link-string";
var returnState = getType(state);
state.linkHref = false;
return returnState;
}
stream.match(linkRE[endChar])
state.linkHref = true;
return getType(state);
};
}
function footnoteLink(stream, state) {
if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) {
state.f = footnoteLinkInside;
stream.next(); // Consume [
if (modeCfg.highlightFormatting) state.formatting = "link";
state.linkText = true;
return getType(state);
}
return switchInline(stream, state, inlineNormal);
}
function footnoteLinkInside(stream, state) {
if (stream.match(']:', true)) {
state.f = state.inline = footnoteUrl;
if (modeCfg.highlightFormatting) state.formatting = "link";
var returnType = getType(state);
state.linkText = false;
return returnType;
}
stream.match(/^([^\]\\]|\\.)+/, true);
return tokenTypes.linkText;
}
function footnoteUrl(stream, state) {
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
// Match URL
stream.match(/^[^\s]+/, true);
// Check for link title
if (stream.peek() === undefined) { // End of line, set flag to check next line
state.linkTitle = true;
} else { // More content on line, check if link title
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\((?:[^)\\]|\\.)+\)))?/, true);
}
state.f = state.inline = inlineNormal;
return tokenTypes.linkHref + " url";
}
var mode = {
startState: function() {
return {
f: blockNormal,
prevLine: {stream: null},
thisLine: {stream: null},
block: blockNormal,
htmlState: null,
indentation: 0,
inline: inlineNormal,
text: handleText,
formatting: false,
linkText: false,
linkHref: false,
linkTitle: false,
code: 0,
em: false,
strong: false,
header: 0,
setext: 0,
hr: false,
taskList: false,
list: false,
listStack: [],
quote: 0,
trailingSpace: 0,
trailingSpaceNewLine: false,
strikethrough: false,
emoji: false,
fencedEndRE: null
};
},
copyState: function(s) {
return {
f: s.f,
prevLine: s.prevLine,
thisLine: s.thisLine,
block: s.block,
htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
indentation: s.indentation,
localMode: s.localMode,
localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
inline: s.inline,
text: s.text,
formatting: false,
linkText: s.linkText,
linkTitle: s.linkTitle,
linkHref: s.linkHref,
code: s.code,
em: s.em,
strong: s.strong,
strikethrough: s.strikethrough,
emoji: s.emoji,
header: s.header,
setext: s.setext,
hr: s.hr,
taskList: s.taskList,
list: s.list,
listStack: s.listStack.slice(0),
quote: s.quote,
indentedCode: s.indentedCode,
trailingSpace: s.trailingSpace,
trailingSpaceNewLine: s.trailingSpaceNewLine,
md_inside: s.md_inside,
fencedEndRE: s.fencedEndRE
};
},
token: function(stream, state) {
// Reset state.formatting
state.formatting = false;
if (stream != state.thisLine.stream) {
state.header = 0;
state.hr = false;
if (stream.match(/^\s*$/, true)) {
blankLine(state);
return null;
}
state.prevLine = state.thisLine
state.thisLine = {stream: stream}
// Reset state.taskList
state.taskList = false;
// Reset state.trailingSpace
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
if (!state.localState) {
state.f = state.block;
if (state.f != htmlBlock) {
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, expandedTab).length;
state.indentation = indentation;
state.indentationDiff = null;
if (indentation > 0) return null;
}
}
}
return state.f(stream, state);
},
innerMode: function(state) {
if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};
if (state.localState) return {state: state.localState, mode: state.localMode};
return {state: state, mode: mode};
},
indent: function(state, textAfter, line) {
if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line)
if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line)
return CodeMirror.Pass
},
blankLine: blankLine,
getType: getType,
blockCommentStart: "<!--",
blockCommentEnd: "-->",
closeBrackets: "()[]{}''\"\"``",
fold: "markdown"
};
return mode;
}, "xml");
CodeMirror.defineMIME("text/markdown", "markdown");
CodeMirror.defineMIME("text/x-markdown", "markdown");
});
@@ -0,0 +1,417 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var htmlConfig = {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true, 'menuitem': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
contextGrabbers: {
'dd': {'dd': true, 'dt': true},
'dt': {'dd': true, 'dt': true},
'li': {'li': true},
'option': {'option': true, 'optgroup': true},
'optgroup': {'optgroup': true},
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
'rp': {'rp': true, 'rt': true},
'rt': {'rp': true, 'rt': true},
'tbody': {'tbody': true, 'tfoot': true},
'td': {'td': true, 'th': true},
'tfoot': {'tbody': true},
'th': {'td': true, 'th': true},
'thead': {'tbody': true, 'tfoot': true},
'tr': {'tr': true}
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true,
caseFold: true
}
var xmlConfig = {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false,
allowMissingTagName: false,
caseFold: false
}
CodeMirror.defineMode("xml", function(editorConf, config_) {
var indentUnit = editorConf.indentUnit
var config = {}
var defaults = config_.htmlMode ? htmlConfig : xmlConfig
for (var prop in defaults) config[prop] = defaults[prop]
for (var prop in config_) config[prop] = config_[prop]
// Return variables for tokenizers
var type, setStyle;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
} else if (stream.match("--")) {
return chain(inBlock("comment", "-->"));
} else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
} else {
return null;
}
} else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
} else {
type = stream.eat("/") ? "closeTag" : "openTag";
state.tokenize = inTag;
return "tag bracket";
}
} else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
} else {
stream.eatWhile(/[^&<]/);
return null;
}
}
inText.isInText = true;
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag bracket";
} else if (ch == "=") {
type = "equals";
return null;
} else if (ch == "<") {
state.tokenize = inText;
state.state = baseState;
state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " tag error" : "tag error";
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
state.stringStartCol = stream.column();
return state.tokenize(stream, state);
} else {
stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
return "word";
}
}
function inAttribute(quote) {
var closure = function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
closure.isInAttribute = true;
return closure;
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
}
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
function lower(tagName) {
return tagName && tagName.toLowerCase();
}
function Context(state, tagName, startOfLine) {
this.prev = state.context;
this.tagName = tagName || "";
this.indent = state.indented;
this.startOfLine = startOfLine;
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
this.noIndent = true;
}
function popContext(state) {
if (state.context) state.context = state.context.prev;
}
function maybePopContext(state, nextTagName) {
var parentTagName;
while (true) {
if (!state.context) {
return;
}
parentTagName = state.context.tagName;
if (!config.contextGrabbers.hasOwnProperty(lower(parentTagName)) ||
!config.contextGrabbers[lower(parentTagName)].hasOwnProperty(lower(nextTagName))) {
return;
}
popContext(state);
}
}
function baseState(type, stream, state) {
if (type == "openTag") {
state.tagStart = stream.column();
return tagNameState;
} else if (type == "closeTag") {
return closeTagNameState;
} else {
return baseState;
}
}
function tagNameState(type, stream, state) {
if (type == "word") {
state.tagName = stream.current();
setStyle = "tag";
return attrState;
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return attrState(type, stream, state);
} else {
setStyle = "error";
return tagNameState;
}
}
function closeTagNameState(type, stream, state) {
if (type == "word") {
var tagName = stream.current();
if (state.context && state.context.tagName != tagName &&
config.implicitlyClosed.hasOwnProperty(lower(state.context.tagName)))
popContext(state);
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
setStyle = "tag";
return closeState;
} else {
setStyle = "tag error";
return closeStateErr;
}
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return closeState(type, stream, state);
} else {
setStyle = "error";
return closeStateErr;
}
}
function closeState(type, _stream, state) {
if (type != "endTag") {
setStyle = "error";
return closeState;
}
popContext(state);
return baseState;
}
function closeStateErr(type, stream, state) {
setStyle = "error";
return closeState(type, stream, state);
}
function attrState(type, _stream, state) {
if (type == "word") {
setStyle = "attribute";
return attrEqState;
} else if (type == "endTag" || type == "selfcloseTag") {
var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null;
if (type == "selfcloseTag" ||
config.autoSelfClosers.hasOwnProperty(lower(tagName))) {
maybePopContext(state, tagName);
} else {
maybePopContext(state, tagName);
state.context = new Context(state, tagName, tagStart == state.indented);
}
return baseState;
}
setStyle = "error";
return attrState;
}
function attrEqState(type, stream, state) {
if (type == "equals") return attrValueState;
if (!config.allowMissing) setStyle = "error";
return attrState(type, stream, state);
}
function attrValueState(type, stream, state) {
if (type == "string") return attrContinuedState;
if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
setStyle = "error";
return attrState(type, stream, state);
}
function attrContinuedState(type, stream, state) {
if (type == "string") return attrContinuedState;
return attrState(type, stream, state);
}
return {
startState: function(baseIndent) {
var state = {tokenize: inText,
state: baseState,
indented: baseIndent || 0,
tagName: null, tagStart: null,
context: null}
if (baseIndent != null) state.baseIndent = baseIndent
return state
},
token: function(stream, state) {
if (!state.tagName && stream.sol())
state.indented = stream.indentation();
if (stream.eatSpace()) return null;
type = null;
var style = state.tokenize(stream, state);
if ((style || type) && style != "comment") {
setStyle = null;
state.state = state.state(type || style, stream, state);
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
}
return style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
// Indent multi-line strings (e.g. css).
if (state.tokenize.isInAttribute) {
if (state.tagStart == state.indented)
return state.stringStartCol + 1;
else
return state.indented + indentUnit;
}
if (context && context.noIndent) return CodeMirror.Pass;
if (state.tokenize != inTag && state.tokenize != inText)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
// Indent the starts of attribute names.
if (state.tagName) {
if (config.multilineTagIndentPastTag !== false)
return state.tagStart + state.tagName.length + 2;
else
return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
}
if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
if (tagAfter && tagAfter[1]) { // Closing tag spotted
while (context) {
if (context.tagName == tagAfter[2]) {
context = context.prev;
break;
} else if (config.implicitlyClosed.hasOwnProperty(lower(context.tagName))) {
context = context.prev;
} else {
break;
}
}
} else if (tagAfter) { // Opening tag spotted
while (context) {
var grabbers = config.contextGrabbers[lower(context.tagName)];
if (grabbers && grabbers.hasOwnProperty(lower(tagAfter[2])))
context = context.prev;
else
break;
}
}
while (context && context.prev && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return state.baseIndent || 0;
},
electricInput: /<\/[\s\w:]+>$/,
blockCommentStart: "<!--",
blockCommentEnd: "-->",
configuration: config.htmlMode ? "html" : "xml",
helperType: config.htmlMode ? "html" : "xml",
skipAttribute: function(state) {
if (state.state == attrValueState)
state.state = attrState
},
xmlCurrentTag: function(state) {
return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
},
xmlCurrentContext: function(state) {
var context = []
for (var cx = state.context; cx; cx = cx.prev)
context.push(cx.tagName)
return context.reverse()
}
};
});
CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
});
@@ -0,0 +1,193 @@
(function( $ ) {
'use strict';
$(document).ready( function() {
var itemList = $('#item-list'), // Container of item list
maxLevel = 6,
dragAxis = false,
dragTabSize = 20,
hierarchicalValue = ( typeof contentOrderSort.hierarchical !== 'undefined' ) ? contentOrderSort.hierarchical : contentOrderSort.hirarchical,
isHierarchical = ( String( hierarchicalValue ) === 'true' ),
disableParentChange = ! isHierarchical,
isSavingOrder = false,
sort_started = {}, // For data related to the dragged element when dragging started
sort_finished = {}; // For data related to the dragged element when dragging has finished
// console.log(contentOrderSort); // Data passed from PHP via wp_localize_script
if ( ! isHierarchical ) {
// Non-hierarchical lists should only support top-level reordering.
maxLevel = 1;
// Locking drag to vertical axis prevents right-drift from triggering invalid nest attempts.
dragAxis = 'y';
// Keep placeholder-based drop valid even when pointer drifts right.
dragTabSize = 9999;
}
// Make item list into nested sortable
// Ref: https://api.jqueryui.com/sortable/
// Ref: https://github.com/ilikenwf/nestedSortable
// See options at https://github.com/ilikenwf/nestedSortable/blob/master/jquery.mjs.nestedSortable.js#L37
// Ref: https://ilikenwf.github.io/example.html (demo)
itemList.nestedSortable({
// Disable nesting if set to true
protectRoot: false,
// Disable moving sub-item to a different parent or up the nested structure
disableParentChange: disableParentChange,
isTree: true,
// Forces the placeholder to have a size.
forcePlaceholderSize: true,
// Restricts sort start click to the specified element.
// Allows for a helper element to be used for dragging display.
// If set to "clone", then the element will be cloned and the clone will be dragged.
helper: 'clone',
// Use vertical-only dragging for non-hierarchical post types.
axis: dragAxis,
listType: 'ul',
items: 'li',
toleranceElement: '> div', // Direct children of the li element
handle: 'div', // The same <div> for toleranceElement is set as the drag handle
// Specifies which mode to use for testing whether the item being moved is hovering over another item.
// If set to 'pointr', is when the mouse pointer overlaps the other item.
tolerance: 'pointer',
// The maximum depth of nested items the list can accept.
maxLevels: maxLevel,
// Defines the opacity of the helper while sorting.
opacity: 0.6,
placeholder: 'ui-sortable-placeholder',
// Whether the sortable items should revert to their new positions using a smooth animation.
// If set as a number, it's in miliseconds
revert: 250,
// How far right or left (in pixels) the item has to travel
// in order to be nested or to be sent outside its current list. Default: 20
tabSize: dragTabSize,
// This event is triggered when sorting starts.
start: function (event, ui) {
if ( isSavingOrder ) {
return false;
}
// console.log('ui.item -- start');
// console.log(ui.item);
sort_started.item = ui.item; // The jQuery object representing the current dragged element.
sort_started.prev = ui.item.prev(':not(".ui-sortable-placeholder")');
sort_started.next = ui.item.next(':not(".ui-sortable-placeholder")');
},
// This event is triggered when the user stopped sorting and the DOM position has changed.
update: function (event, ui) {
// console.log('ui.item -- update');
// console.log(ui.item);
// Elements of the "Updating order..." notice
var updateNotice = $('#updating-order-notice'), // Wrapper
spinner = $('#spinner-img'), // Spinner
updateSuccess = $('.updating-order-notice .dashicons.dashicons-saved'); // Check mark
ui.item.find('div.row-content:first').append(updateNotice);
// Reset the state of the "Updating order..." indicator
$(spinner).show();
$(updateSuccess).hide();
$(updateNotice).css('background-color','#eee').fadeIn();
// Get the end items where the item was placed
// console.log('sort_finished');
// console.log(sort_finished);
sort_finished.item = ui.item; // The jQuery object representing the current dragged element.
// sort_finished.prev = ui.item.prev(':not(".ui-sortable-placeholder")');
// sort_finished.next = ui.item.next(':not(".ui-sortable-placeholder")');
// If an item is moved as a child of another item
// it will be inserted inside the ul.child-list with a data-parent
if ( ui.item.parent('.child-list').length ) {
// if ( ui.item.siblings('.list-item').length ) {
// Do something
// } else {
// Let's set the item's parent ID here, taking from ul.child-list's data-parent info
sort_finished.item.attr('data-parent', ui.item.parent('.child-list').attr('data-parent'));
// }
} else {
sort_finished.item.attr('data-parent', '0');
}
// console.log('sort_finished.prev');
// console.log(sort_finished.prev);
var list_offset = parseInt(sort_finished.item.index());
sort_finished.item.attr('data-menu-order', list_offset);
// Get attributes
var attributes = {};
$.each(sort_finished.item[0].attributes, function () {
attributes[this.name] = this.value;
});
// console.log('attributes: ' + cleanStringify(attributes));
// Data for ajax call
var dataArgs = {
action: contentOrderSort.action, // from wp_localize_script
item_parent: 0, // We only deal with top-level items, not child items
start: 0, // Start processing menu_order update in DB from item with menu_order defined here
nonce: contentOrderSort.nonce,
post_id: sort_finished.item.attr('data-id'),
menu_order: sort_finished.item.attr('data-menu-order'),
excluded_items: {},
post_type: sort_started.item.attr('data-post-type'),
attributes: attributes,
};
// console.log('dataArgs: ' + cleanStringify(dataArgs));
isSavingOrder = true;
// AJAX call to update menu_order for items in the list
$.ajax({
type: "POST",
url: ajaxurl,
data: dataArgs,
success: function(response) {
// console.log(response);
// Update the state of the "Updating order..." indicator
$(spinner).hide();
$(updateSuccess).show();
$(updateNotice).css('background-color','#cce5cc').delay(1000).fadeOut();
},
error: function(errorThrown) {
console.log(errorThrown);
},
complete: function() {
isSavingOrder = false;
}
});
}
});
});
// Convert object to simpler string for console.log. Ref: https://stackoverflow.com/a/48845206
// function cleanStringify(object) {
// if (object && typeof object === 'object') {
// object = copyWithoutCircularReferences([object], object);
// }
// return JSON.stringify(object);
// function copyWithoutCircularReferences(references, object) {
// var cleanObject = {};
// Object.keys(object).forEach(function(key) {
// var value = object[key];
// if (value && typeof value === 'object') {
// if (references.indexOf(value) < 0) {
// references.push(value);
// cleanObject[key] = copyWithoutCircularReferences(references, value);
// references.pop();
// } else {
// cleanObject[key] = '###_Circular_###';
// }
// } else if (typeof value !== 'function') {
// cleanObject[key] = value;
// }
// });
// return cleanObject;
// }
// }
})( jQuery );
@@ -0,0 +1,127 @@
(function( $ ) {
'use strict';
/**
* Get an element by ID without using CSS selector parsing.
*
* This avoids jQuery selector syntax errors when IDs contain percent-encoded
* sequences (e.g. %e3%83...) which can happen for multibyte site languages.
*
* @param {string} id Element ID (without '#').
* @return {Object} jQuery object (empty set when not found).
*/
function asenhaGetById( id ) {
var el = document.getElementById( id );
return el ? $( el ) : $();
}
// Get localized strings with fallback to empty object
var strings = (typeof amoPageVars !== 'undefined' && amoPageVars.strings) ? amoPageVars.strings : {};
$(document).ready( function() {
// ----- Menu Ordering -----
// Initialize sortable elements for parent menu items: https://api.jqueryui.com/sortable/
$('#custom-admin-menu').sortable({
items: '> li',
opacity: 0.6,
placeholder: 'sortable-placeholder',
tolerance: 'pointer',
revert: 250
});
// Get the default/current menu order
let menuOrder = $('#custom-admin-menu').sortable("toArray").toString();
// Set hidden input value for saving in options
document.getElementById('custom_menu_order').value = menuOrder;
// Save custom order into a comma-separated string, triggerred after each drag and drop of menu item
// https://api.jqueryui.com/sortable/#event-update
// https://api.jqueryui.com/sortable/#method-toArray
$('#custom-admin-menu').on( 'sortupdate', function( event, ui) {
// Get the updated menu order
let menuOrder = $('#custom-admin-menu').sortable("toArray").toString();
// Set hidden input value for saving in options
document.getElementById('custom_menu_order').value = menuOrder;
});
// ----- Parent Menu Item Hiding -----
// Prepare constant to store IDs of menu items that will be hidden
if ( document.getElementById('custom_menu_hidden') != null ) {
var hiddenMenuItems = document.getElementById('custom_menu_hidden').value.split(","); // array
} else {
var hiddenMenuItems = []; // array
}
// Detect which menu items are being checked. Ref: https://stackoverflow.com/a/3871602
Array.from(document.getElementsByClassName('parent-menu-hide-checkbox')).forEach(function(item,index,array) {
item.addEventListener('click', event => {
if (event.target.checked) {
// Add ID of menu item to array
hiddenMenuItems.push(event.target.dataset.menuItemId);
} else {
// Remove ID of menu item from array
const start = hiddenMenuItems.indexOf(event.target.dataset.menuItemId);
const deleteCount = 1;
hiddenMenuItems.splice(start, deleteCount);
}
// Set hidden input value
document.getElementById('custom_menu_hidden').value = hiddenMenuItems;
});
});
// Clicking on header save button
$('#amo-save-changes').click( function(e) {
e.preventDefault();
// Prepare variable to store ID-Title pairs of menu items
var customMenuTitles = []; // empty array
// Initialize other variables
var menuItemId = '';
var customTitle = '';
// Save default/custom title values. Ref: https://stackoverflow.com/a/3871602
Array.from(document.getElementsByClassName('menu-item-custom-title')).forEach(function(item,index,array) {
menuItemId = item.dataset.menuItemId;
customTitle = item.value;
customMenuTitles.push(menuItemId + '__' + customTitle);
});
// Set hidden input value
document.getElementById('custom_menu_titles').value = customMenuTitles;
});
}); // End of $(document).ready()
})( jQuery );
@@ -0,0 +1,213 @@
/*
* This combined file was created by the DataTables downloader builder:
* https://datatables.net/download
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#dt/dt-1.12.1
*
* Included libraries:
* DataTables 1.12.1
*/
/*!
SpryMedia Ltd.
This source file is free software, available under the following license:
MIT license - http://datatables.net/license
This source file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
For details please refer to: http://www.datatables.net
DataTables 1.12.1
©2008-2022 SpryMedia Ltd - datatables.net/license
*/
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(l,y,A){l instanceof String&&(l=String(l));for(var q=l.length,E=0;E<q;E++){var P=l[E];if(y.call(A,P,E,l))return{i:E,v:P}}return{i:-1,v:void 0}};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.ISOLATE_POLYFILLS=!1;
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(l,y,A){if(l==Array.prototype||l==Object.prototype)return l;l[y]=A.value;return l};$jscomp.getGlobal=function(l){l=["object"==typeof globalThis&&globalThis,l,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var y=0;y<l.length;++y){var A=l[y];if(A&&A.Math==Math)return A}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";var $jscomp$lookupPolyfilledValue=function(l,y){var A=$jscomp.propertyToPolyfillSymbol[y];if(null==A)return l[y];A=l[A];return void 0!==A?A:l[y]};
$jscomp.polyfill=function(l,y,A,q){y&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(l,y,A,q):$jscomp.polyfillUnisolated(l,y,A,q))};$jscomp.polyfillUnisolated=function(l,y,A,q){A=$jscomp.global;l=l.split(".");for(q=0;q<l.length-1;q++){var E=l[q];if(!(E in A))return;A=A[E]}l=l[l.length-1];q=A[l];y=y(q);y!=q&&null!=y&&$jscomp.defineProperty(A,l,{configurable:!0,writable:!0,value:y})};
$jscomp.polyfillIsolated=function(l,y,A,q){var E=l.split(".");l=1===E.length;q=E[0];q=!l&&q in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var P=0;P<E.length-1;P++){var la=E[P];if(!(la in q))return;q=q[la]}E=E[E.length-1];A=$jscomp.IS_SYMBOL_NATIVE&&"es6"===A?q[E]:null;y=y(A);null!=y&&(l?$jscomp.defineProperty($jscomp.polyfills,E,{configurable:!0,writable:!0,value:y}):y!==A&&($jscomp.propertyToPolyfillSymbol[E]=$jscomp.IS_SYMBOL_NATIVE?$jscomp.global.Symbol(E):$jscomp.POLYFILL_PREFIX+E,
E=$jscomp.propertyToPolyfillSymbol[E],$jscomp.defineProperty(q,E,{configurable:!0,writable:!0,value:y})))};$jscomp.polyfill("Array.prototype.find",function(l){return l?l:function(y,A){return $jscomp.findInternal(this,y,A).v}},"es6","es3");
(function(l){"function"===typeof define&&define.amd?define(["jquery"],function(y){return l(y,window,document)}):"object"===typeof exports?module.exports=function(y,A){y||(y=window);A||(A="undefined"!==typeof window?require("jquery"):require("jquery")(y));return l(A,y,y.document)}:window.DataTable=l(jQuery,window,document)})(function(l,y,A,q){function E(a){var b,c,d={};l.each(a,function(e,h){(b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" ")&&(c=e.replace(b[0],
b[2].toLowerCase()),d[c]=e,"o"===b[1]&&E(a[e]))});a._hungarianMap=d}function P(a,b,c){a._hungarianMap||E(a);var d;l.each(b,function(e,h){d=a._hungarianMap[e];d===q||!c&&b[d]!==q||("o"===d.charAt(0)?(b[d]||(b[d]={}),l.extend(!0,b[d],b[e]),P(a[d],b[d],c)):b[d]=b[e])})}function la(a){var b=u.defaults.oLanguage,c=b.sDecimal;c&&bb(c);if(a){var d=a.sZeroRecords;!a.sEmptyTable&&d&&"No data available in table"===b.sEmptyTable&&Y(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&d&&"Loading..."===b.sLoadingRecords&&
Y(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&c!==a&&bb(a)}}function Db(a){S(a,"ordering","bSort");S(a,"orderMulti","bSortMulti");S(a,"orderClasses","bSortClasses");S(a,"orderCellsTop","bSortCellsTop");S(a,"order","aaSorting");S(a,"orderFixed","aaSortingFixed");S(a,"paging","bPaginate");S(a,"pagingType","sPaginationType");S(a,"pageLength","iDisplayLength");S(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":
"");"boolean"===typeof a.scrollX&&(a.scrollX=a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&P(u.models.oSearch,a[b])}function Eb(a){S(a,"orderable","bSortable");S(a,"orderData","aDataSort");S(a,"orderSequence","asSorting");S(a,"orderDataType","sortDataType");var b=a.aDataSort;"number"!==typeof b||Array.isArray(b)||(a.aDataSort=[b])}function Fb(a){if(!u.__browser){var b={};u.__browser=b;var c=l("<div/>").css({position:"fixed",top:0,left:-1*l(y).scrollLeft(),height:1,
width:1,overflow:"hidden"}).append(l("<div/>").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(l("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}l.extend(a.oBrowser,u.__browser);a.oScroll.iBarWidth=u.__browser.barWidth}
function Gb(a,b,c,d,e,h){var f=!1;if(c!==q){var g=c;f=!0}for(;d!==e;)a.hasOwnProperty(d)&&(g=f?b(g,a[d],d,a):a[d],f=!0,d+=h);return g}function cb(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=l.extend({},u.models.oColumn,c,{nTh:b?b:A.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=l.extend({},u.models.oSearch,c[d]);Ia(a,d,l(b).data())}function Ia(a,b,c){b=a.aoColumns[b];
var d=a.oClasses,e=l(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var h=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);h&&(b.sWidthOrig=h[1])}c!==q&&null!==c&&(Eb(c),P(u.defaults.column,c,!0),c.mDataProp===q||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),h=b.sClass,l.extend(b,c),Y(b,c,"sWidth","sWidthOrig"),h!==b.sClass&&(b.sClass=h+" "+b.sClass),c.iDataSort!==q&&(b.aDataSort=[c.iDataSort]),
Y(b,c,"aDataSort"));var f=b.mData,g=ma(f),k=b.mRender?ma(b.mRender):null;c=function(m){return"string"===typeof m&&-1!==m.indexOf("@")};b._bAttrSrc=l.isPlainObject(f)&&(c(f.sort)||c(f.type)||c(f.filter));b._setter=null;b.fnGetData=function(m,n,p){var t=g(m,n,q,p);return k&&n?k(t,n,m,p):t};b.fnSetData=function(m,n,p){return ha(f)(m,n,p)};"number"!==typeof f&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==l.inArray("asc",b.asSorting);c=-1!==l.inArray("desc",
b.asSorting);b.bSortable&&(a||c)?a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function sa(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;db(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;""===b.sY&&""===b.sX||Ja(a);F(a,null,"column-sizing",
[a])}function ta(a,b){a=Ka(a,"bVisible");return"number"===typeof a[b]?a[b]:null}function ua(a,b){a=Ka(a,"bVisible");b=l.inArray(b,a);return-1!==b?b:null}function na(a){var b=0;l.each(a.aoColumns,function(c,d){d.bVisible&&"none"!==l(d.nTh).css("display")&&b++});return b}function Ka(a,b){var c=[];l.map(a.aoColumns,function(d,e){d[b]&&c.push(e)});return c}function eb(a){var b=a.aoColumns,c=a.aoData,d=u.ext.type.detect,e,h,f;var g=0;for(e=b.length;g<e;g++){var k=b[g];var m=[];if(!k.sType&&k._sManualType)k.sType=
k._sManualType;else if(!k.sType){var n=0;for(h=d.length;n<h;n++){var p=0;for(f=c.length;p<f;p++){m[p]===q&&(m[p]=T(a,p,g,"type"));var t=d[n](m[p],a);if(!t&&n!==d.length-1)break;if("html"===t&&!aa(m[p]))break}if(t){k.sType=t;break}}k.sType||(k.sType="string")}}}function Hb(a,b,c,d){var e,h,f,g=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){var k=b[e];var m=k.target!==q?k.target:k.targets!==q?k.targets:k.aTargets;Array.isArray(m)||(m=[m]);var n=0;for(h=m.length;n<h;n++)if("number"===typeof m[n]&&0<=m[n]){for(;g.length<=
m[n];)cb(a);d(m[n],k)}else if("number"===typeof m[n]&&0>m[n])d(g.length+m[n],k);else if("string"===typeof m[n]){var p=0;for(f=g.length;p<f;p++)("_all"==m[n]||l(g[p].nTh).hasClass(m[n]))&&d(p,k)}}if(c)for(e=0,a=c.length;e<a;e++)d(e,c[e])}function ia(a,b,c,d){var e=a.aoData.length,h=l.extend(!0,{},u.models.oRow,{src:c?"dom":"data",idx:e});h._aData=b;a.aoData.push(h);for(var f=a.aoColumns,g=0,k=f.length;g<k;g++)f[g].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==q&&(a.aIds[b]=h);!c&&a.oFeatures.bDeferRender||
fb(a,e,c,d);return e}function La(a,b){var c;b instanceof l||(b=l(b));return b.map(function(d,e){c=gb(a,e);return ia(a,c.data,e,c.cells)})}function T(a,b,c,d){"search"===d?d="filter":"order"===d&&(d="sort");var e=a.iDraw,h=a.aoColumns[c],f=a.aoData[b]._aData,g=h.sDefaultContent,k=h.fnGetData(f,d,{settings:a,row:b,col:c});if(k===q)return a.iDrawError!=e&&null===g&&(ea(a,0,"Requested unknown parameter "+("function"==typeof h.mData?"{function}":"'"+h.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=
e),g;if((k===f||null===k)&&null!==g&&d!==q)k=g;else if("function"===typeof k)return k.call(f);if(null===k&&"display"===d)return"";"filter"===d&&(a=u.ext.type.search,a[h.sType]&&(k=a[h.sType](k)));return k}function Ib(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}function hb(a){return l.map(a.match(/(\\.|[^\.])+/g)||[""],function(b){return b.replace(/\\\./g,".")})}function ib(a){return U(a.aoData,"_aData")}function Ma(a){a.aoData.length=0;a.aiDisplayMaster.length=
0;a.aiDisplay.length=0;a.aIds={}}function Na(a,b,c){for(var d=-1,e=0,h=a.length;e<h;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===q&&a.splice(d,1)}function va(a,b,c,d){var e=a.aoData[b],h,f=function(k,m){for(;k.childNodes.length;)k.removeChild(k.firstChild);k.innerHTML=T(a,b,m,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var g=e.anCells;if(g)if(d!==q)f(g[d],d);else for(c=0,h=g.length;c<h;c++)f(g[c],c)}else e._aData=gb(a,e,d,d===q?q:e._aData).data;e._aSortData=null;e._aFilterData=null;f=
a.aoColumns;if(d!==q)f[d].sType=null;else{c=0;for(h=f.length;c<h;c++)f[c].sType=null;jb(a,e)}}function gb(a,b,c,d){var e=[],h=b.firstChild,f,g=0,k,m=a.aoColumns,n=a._rowReadObject;d=d!==q?d:n?{}:[];var p=function(x,w){if("string"===typeof x){var r=x.indexOf("@");-1!==r&&(r=x.substring(r+1),ha(x)(d,w.getAttribute(r)))}},t=function(x){if(c===q||c===g)f=m[g],k=x.innerHTML.trim(),f&&f._bAttrSrc?(ha(f.mData._)(d,k),p(f.mData.sort,x),p(f.mData.type,x),p(f.mData.filter,x)):n?(f._setter||(f._setter=ha(f.mData)),
f._setter(d,k)):d[g]=k;g++};if(h)for(;h;){var v=h.nodeName.toUpperCase();if("TD"==v||"TH"==v)t(h),e.push(h);h=h.nextSibling}else for(e=b.anCells,h=0,v=e.length;h<v;h++)t(e[h]);(b=b.firstChild?b:b.nTr)&&(b=b.getAttribute("id"))&&ha(a.rowId)(d,b);return{data:d,cells:e}}function fb(a,b,c,d){var e=a.aoData[b],h=e._aData,f=[],g,k;if(null===e.nTr){var m=c||A.createElement("tr");e.nTr=m;e.anCells=f;m._DT_RowIndex=b;jb(a,e);var n=0;for(g=a.aoColumns.length;n<g;n++){var p=a.aoColumns[n];e=(k=c?!1:!0)?A.createElement(p.sCellType):
d[n];e._DT_CellIndex={row:b,column:n};f.push(e);if(k||!(!p.mRender&&p.mData===n||l.isPlainObject(p.mData)&&p.mData._===n+".display"))e.innerHTML=T(a,b,n,"display");p.sClass&&(e.className+=" "+p.sClass);p.bVisible&&!c?m.appendChild(e):!p.bVisible&&c&&e.parentNode.removeChild(e);p.fnCreatedCell&&p.fnCreatedCell.call(a.oInstance,e,T(a,b,n),h,b,n)}F(a,"aoRowCreatedCallback",null,[m,h,b,f])}}function jb(a,b){var c=b.nTr,d=b._aData;if(c){if(a=a.rowIdFn(d))c.id=a;d.DT_RowClass&&(a=d.DT_RowClass.split(" "),
b.__rowc=b.__rowc?Oa(b.__rowc.concat(a)):a,l(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&l(c).attr(d.DT_RowAttr);d.DT_RowData&&l(c).data(d.DT_RowData)}}function Jb(a){var b,c,d=a.nTHead,e=a.nTFoot,h=0===l("th, td",d).length,f=a.oClasses,g=a.aoColumns;h&&(c=l("<tr/>").appendTo(d));var k=0;for(b=g.length;k<b;k++){var m=g[k];var n=l(m.nTh).addClass(m.sClass);h&&n.appendTo(c);a.oFeatures.bSort&&(n.addClass(m.sSortingClass),!1!==m.bSortable&&(n.attr("tabindex",a.iTabIndex).attr("aria-controls",
a.sTableId),kb(a,m.nTh,k)));m.sTitle!=n[0].innerHTML&&n.html(m.sTitle);lb(a,"header")(a,n,m,f)}h&&wa(a.aoHeader,d);l(d).children("tr").children("th, td").addClass(f.sHeaderTH);l(e).children("tr").children("th, td").addClass(f.sFooterTH);if(null!==e)for(a=a.aoFooter[0],k=0,b=a.length;k<b;k++)m=g[k],m.nTf=a[k].cell,m.sClass&&l(m.nTf).addClass(m.sClass)}function xa(a,b,c){var d,e,h=[],f=[],g=a.aoColumns.length;if(b){c===q&&(c=!1);var k=0;for(d=b.length;k<d;k++){h[k]=b[k].slice();h[k].nTr=b[k].nTr;for(e=
g-1;0<=e;e--)a.aoColumns[e].bVisible||c||h[k].splice(e,1);f.push([])}k=0;for(d=h.length;k<d;k++){if(a=h[k].nTr)for(;e=a.firstChild;)a.removeChild(e);e=0;for(b=h[k].length;e<b;e++){var m=g=1;if(f[k][e]===q){a.appendChild(h[k][e].cell);for(f[k][e]=1;h[k+g]!==q&&h[k][e].cell==h[k+g][e].cell;)f[k+g][e]=1,g++;for(;h[k][e+m]!==q&&h[k][e].cell==h[k][e+m].cell;){for(c=0;c<g;c++)f[k+c][e+m]=1;m++}l(h[k][e].cell).attr("rowspan",g).attr("colspan",m)}}}}}function ja(a,b){var c="ssp"==Q(a),d=a.iInitDisplayStart;
d!==q&&-1!==d&&(a._iDisplayStart=c?d:d>=a.fnRecordsDisplay()?0:d,a.iInitDisplayStart=-1);c=F(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==l.inArray(!1,c))V(a,!1);else{c=[];var e=0;d=a.asStripeClasses;var h=d.length,f=a.oLanguage,g="ssp"==Q(a),k=a.aiDisplay,m=a._iDisplayStart,n=a.fnDisplayEnd();a.bDrawing=!0;if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,V(a,!1);else if(!g)a.iDraw++;else if(!a.bDestroying&&!b){Kb(a);return}if(0!==k.length)for(b=g?a.aoData.length:n,f=g?0:m;f<b;f++){g=k[f];var p=a.aoData[g];
null===p.nTr&&fb(a,g);var t=p.nTr;if(0!==h){var v=d[e%h];p._sRowStripe!=v&&(l(t).removeClass(p._sRowStripe).addClass(v),p._sRowStripe=v)}F(a,"aoRowCallback",null,[t,p._aData,e,f,g]);c.push(t);e++}else e=f.sZeroRecords,1==a.iDraw&&"ajax"==Q(a)?e=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(e=f.sEmptyTable),c[0]=l("<tr/>",{"class":h?d[0]:""}).append(l("<td />",{valign:"top",colSpan:na(a),"class":a.oClasses.sRowEmpty}).html(e))[0];F(a,"aoHeaderCallback","header",[l(a.nTHead).children("tr")[0],
ib(a),m,n,k]);F(a,"aoFooterCallback","footer",[l(a.nTFoot).children("tr")[0],ib(a),m,n,k]);d=l(a.nTBody);d.children().detach();d.append(l(c));F(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ka(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Lb(a);d?ya(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;ja(a);a._drawHold=!1}function Mb(a){var b=a.oClasses,c=l(a.nTable);c=l("<div/>").insertBefore(c);var d=a.oFeatures,
e=l("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var h=a.sDom.split(""),f,g,k,m,n,p,t=0;t<h.length;t++){f=null;g=h[t];if("<"==g){k=l("<div/>")[0];m=h[t+1];if("'"==m||'"'==m){n="";for(p=2;h[t+p]!=m;)n+=h[t+p],p++;"H"==n?n=b.sJUIHeader:"F"==n&&(n=b.sJUIFooter);-1!=n.indexOf(".")?(m=n.split("."),k.id=m[0].substr(1,m[0].length-1),k.className=m[1]):"#"==n.charAt(0)?k.id=n.substr(1,
n.length-1):k.className=n;t+=p}e.append(k);e=l(k)}else if(">"==g)e=e.parent();else if("l"==g&&d.bPaginate&&d.bLengthChange)f=Nb(a);else if("f"==g&&d.bFilter)f=Ob(a);else if("r"==g&&d.bProcessing)f=Pb(a);else if("t"==g)f=Qb(a);else if("i"==g&&d.bInfo)f=Rb(a);else if("p"==g&&d.bPaginate)f=Sb(a);else if(0!==u.ext.feature.length)for(k=u.ext.feature,p=0,m=k.length;p<m;p++)if(g==k[p].cFeature){f=k[p].fnInit(a);break}f&&(k=a.aanFeatures,k[g]||(k[g]=[]),k[g].push(f),e.append(f))}c.replaceWith(e);a.nHolding=
null}function wa(a,b){b=l(b).children("tr");var c,d,e;a.splice(0,a.length);var h=0;for(e=b.length;h<e;h++)a.push([]);h=0;for(e=b.length;h<e;h++){var f=b[h];for(c=f.firstChild;c;){if("TD"==c.nodeName.toUpperCase()||"TH"==c.nodeName.toUpperCase()){var g=1*c.getAttribute("colspan");var k=1*c.getAttribute("rowspan");g=g&&0!==g&&1!==g?g:1;k=k&&0!==k&&1!==k?k:1;var m=0;for(d=a[h];d[m];)m++;var n=m;var p=1===g?!0:!1;for(d=0;d<g;d++)for(m=0;m<k;m++)a[h+m][n+d]={cell:c,unique:p},a[h+m].nTr=f}c=c.nextSibling}}}
function Pa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],wa(c,b)));b=0;for(var e=c.length;b<e;b++)for(var h=0,f=c[b].length;h<f;h++)!c[b][h].unique||d[h]&&a.bSortCellsTop||(d[h]=c[b][h].cell);return d}function Qa(a,b,c){F(a,"aoServerParams","serverParams",[b]);if(b&&Array.isArray(b)){var d={},e=/(.*?)\[\]$/;l.each(b,function(n,p){(n=p.name.match(e))?(n=n[0],d[n]||(d[n]=[]),d[n].push(p.value)):d[p.name]=p.value});b=d}var h=a.ajax,f=a.oInstance,g=function(n){var p=a.jqXHR?a.jqXHR.status:null;if(null===
n||"number"===typeof p&&204==p)n={},za(a,n,[]);(p=n.error||n.sError)&&ea(a,0,p);a.json=n;F(a,null,"xhr",[a,n,a.jqXHR]);c(n)};if(l.isPlainObject(h)&&h.data){var k=h.data;var m="function"===typeof k?k(b,a):k;b="function"===typeof k&&m?m:l.extend(!0,b,m);delete h.data}m={data:b,success:g,dataType:"json",cache:!1,type:a.sServerMethod,error:function(n,p,t){t=F(a,null,"xhr",[a,null,a.jqXHR]);-1===l.inArray(!0,t)&&("parsererror"==p?ea(a,0,"Invalid JSON response",1):4===n.readyState&&ea(a,0,"Ajax error",
7));V(a,!1)}};a.oAjaxData=b;F(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(f,a.sAjaxSource,l.map(b,function(n,p){return{name:p,value:n}}),g,a):a.sAjaxSource||"string"===typeof h?a.jqXHR=l.ajax(l.extend(m,{url:h||a.sAjaxSource})):"function"===typeof h?a.jqXHR=h.call(f,b,g,a):(a.jqXHR=l.ajax(l.extend(m,h)),h.data=k)}function Kb(a){a.iDraw++;V(a,!0);Qa(a,Tb(a),function(b){Ub(a,b)})}function Tb(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,h=a.aoPreSearchCols,f=[],g=oa(a);
var k=a._iDisplayStart;var m=!1!==d.bPaginate?a._iDisplayLength:-1;var n=function(x,w){f.push({name:x,value:w})};n("sEcho",a.iDraw);n("iColumns",c);n("sColumns",U(b,"sName").join(","));n("iDisplayStart",k);n("iDisplayLength",m);var p={draw:a.iDraw,columns:[],order:[],start:k,length:m,search:{value:e.sSearch,regex:e.bRegex}};for(k=0;k<c;k++){var t=b[k];var v=h[k];m="function"==typeof t.mData?"function":t.mData;p.columns.push({data:m,name:t.sName,searchable:t.bSearchable,orderable:t.bSortable,search:{value:v.sSearch,
regex:v.bRegex}});n("mDataProp_"+k,m);d.bFilter&&(n("sSearch_"+k,v.sSearch),n("bRegex_"+k,v.bRegex),n("bSearchable_"+k,t.bSearchable));d.bSort&&n("bSortable_"+k,t.bSortable)}d.bFilter&&(n("sSearch",e.sSearch),n("bRegex",e.bRegex));d.bSort&&(l.each(g,function(x,w){p.order.push({column:w.col,dir:w.dir});n("iSortCol_"+x,w.col);n("sSortDir_"+x,w.dir)}),n("iSortingCols",g.length));b=u.ext.legacy.ajax;return null===b?a.sAjaxSource?f:p:b?f:p}function Ub(a,b){var c=function(f,g){return b[f]!==q?b[f]:b[g]},
d=za(a,b),e=c("sEcho","draw"),h=c("iTotalRecords","recordsTotal");c=c("iTotalDisplayRecords","recordsFiltered");if(e!==q){if(1*e<a.iDraw)return;a.iDraw=1*e}d||(d=[]);Ma(a);a._iRecordsTotal=parseInt(h,10);a._iRecordsDisplay=parseInt(c,10);e=0;for(h=d.length;e<h;e++)ia(a,d[e]);a.aiDisplay=a.aiDisplayMaster.slice();ja(a,!0);a._bInitComplete||Ra(a,b);V(a,!1)}function za(a,b,c){a=l.isPlainObject(a.ajax)&&a.ajax.dataSrc!==q?a.ajax.dataSrc:a.sAjaxDataProp;if(!c)return"data"===a?b.aaData||b[a]:""!==a?ma(a)(b):
b;ha(a)(b,c)}function Ob(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,h=a.aanFeatures,f='<input type="search" class="'+b.sFilterInput+'"/>',g=d.sSearch;g=g.match(/_INPUT_/)?g.replace("_INPUT_",f):g+f;b=l("<div/>",{id:h.f?null:c+"_filter","class":b.sFilter}).append(l("<label/>").append(g));var k=function(n){var p=this.value?this.value:"";e.return&&"Enter"!==n.key||p==e.sSearch||(ya(a,{sSearch:p,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive,"return":e.return}),
a._iDisplayStart=0,ja(a))};h=null!==a.searchDelay?a.searchDelay:"ssp"===Q(a)?400:0;var m=l("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",h?mb(k,h):k).on("mouseup",function(n){setTimeout(function(){k.call(m[0],n)},10)}).on("keypress.DT",function(n){if(13==n.keyCode)return!1}).attr("aria-controls",c);l(a.nTable).on("search.dt.DT",function(n,p){if(a===p)try{m[0]!==A.activeElement&&m.val(e.sSearch)}catch(t){}});return b[0]}function ya(a,
b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,h=function(g){d.sSearch=g.sSearch;d.bRegex=g.bRegex;d.bSmart=g.bSmart;d.bCaseInsensitive=g.bCaseInsensitive;d.return=g.return},f=function(g){return g.bEscapeRegex!==q?!g.bEscapeRegex:g.bRegex};eb(a);if("ssp"!=Q(a)){Vb(a,b.sSearch,c,f(b),b.bSmart,b.bCaseInsensitive,b.return);h(b);for(b=0;b<e.length;b++)Wb(a,e[b].sSearch,b,f(e[b]),e[b].bSmart,e[b].bCaseInsensitive);Xb(a)}else h(b);a.bFiltered=!0;F(a,null,"search",[a])}function Xb(a){for(var b=u.ext.search,
c=a.aiDisplay,d,e,h=0,f=b.length;h<f;h++){for(var g=[],k=0,m=c.length;k<m;k++)e=c[k],d=a.aoData[e],b[h](a,d._aFilterData,e,d._aData,k)&&g.push(e);c.length=0;l.merge(c,g)}}function Wb(a,b,c,d,e,h){if(""!==b){var f=[],g=a.aiDisplay;d=nb(b,d,e,h);for(e=0;e<g.length;e++)b=a.aoData[g[e]]._aFilterData[c],d.test(b)&&f.push(g[e]);a.aiDisplay=f}}function Vb(a,b,c,d,e,h){e=nb(b,d,e,h);var f=a.oPreviousSearch.sSearch,g=a.aiDisplayMaster;h=[];0!==u.ext.search.length&&(c=!0);var k=Yb(a);if(0>=b.length)a.aiDisplay=
g.slice();else{if(k||c||d||f.length>b.length||0!==b.indexOf(f)||a.bSorted)a.aiDisplay=g.slice();b=a.aiDisplay;for(c=0;c<b.length;c++)e.test(a.aoData[b[c]]._sFilterRow)&&h.push(b[c]);a.aiDisplay=h}}function nb(a,b,c,d){a=b?a:ob(a);c&&(a="^(?=.*?"+l.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(e){if('"'===e.charAt(0)){var h=e.match(/^"(.*)"$/);e=h?h[1]:e}return e.replace('"',"")}).join(")(?=.*?")+").*$");return new RegExp(a,d?"i":"")}function Yb(a){var b=a.aoColumns,c,d;var e=!1;var h=0;for(c=a.aoData.length;h<
c;h++){var f=a.aoData[h];if(!f._aFilterData){var g=[];e=0;for(d=b.length;e<d;e++){var k=b[e];k.bSearchable?(k=T(a,h,e,"filter"),null===k&&(k=""),"string"!==typeof k&&k.toString&&(k=k.toString())):k="";k.indexOf&&-1!==k.indexOf("&")&&(Sa.innerHTML=k,k=Bc?Sa.textContent:Sa.innerText);k.replace&&(k=k.replace(/[\r\n\u2028]/g,""));g.push(k)}f._aFilterData=g;f._sFilterRow=g.join(" ");e=!0}}return e}function Zb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}
function $b(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function Rb(a){var b=a.sTableId,c=a.aanFeatures.i,d=l("<div/>",{"class":a.oClasses.sInfo,id:c?null:b+"_info"});c||(a.aoDrawCallback.push({fn:ac,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),l(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function ac(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),h=a.fnRecordsTotal(),
f=a.fnRecordsDisplay(),g=f?c.sInfo:c.sInfoEmpty;f!==h&&(g+=" "+c.sInfoFiltered);g+=c.sInfoPostFix;g=bc(a,g);c=c.fnInfoCallback;null!==c&&(g=c.call(a.oInstance,a,d,e,h,f,g));l(b).html(g)}}function bc(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,h=a.fnRecordsDisplay(),f=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,h)).replace(/_PAGE_/g,c.call(a,f?1:Math.ceil(d/
e))).replace(/_PAGES_/g,c.call(a,f?1:Math.ceil(h/e)))}function Aa(a){var b=a.iInitDisplayStart,c=a.aoColumns;var d=a.oFeatures;var e=a.bDeferLoading;if(a.bInitialised){Mb(a);Jb(a);xa(a,a.aoHeader);xa(a,a.aoFooter);V(a,!0);d.bAutoWidth&&db(a);var h=0;for(d=c.length;h<d;h++){var f=c[h];f.sWidth&&(f.nTh.style.width=K(f.sWidth))}F(a,null,"preInit",[a]);ka(a);c=Q(a);if("ssp"!=c||e)"ajax"==c?Qa(a,[],function(g){var k=za(a,g);for(h=0;h<k.length;h++)ia(a,k[h]);a.iInitDisplayStart=b;ka(a);V(a,!1);Ra(a,g)},
a):(V(a,!1),Ra(a))}else setTimeout(function(){Aa(a)},200)}function Ra(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&sa(a);F(a,null,"plugin-init",[a,b]);F(a,"aoInitComplete","init",[a,b])}function pb(a,b){b=parseInt(b,10);a._iDisplayLength=b;qb(a);F(a,null,"length",[a,b])}function Nb(a){var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=Array.isArray(d[0]),h=e?d[0]:d;d=e?d[1]:d;e=l("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect});for(var f=0,g=h.length;f<g;f++)e[0][f]=new Option("number"===
typeof d[f]?a.fnFormatNumber(d[f]):d[f],h[f]);var k=l("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(k[0].id=c+"_length");k.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));l("select",k).val(a._iDisplayLength).on("change.DT",function(m){pb(a,l(this).val());ja(a)});l(a.nTable).on("length.dt.DT",function(m,n,p){a===n&&l("select",k).val(p)});return k[0]}function Sb(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(f){ja(f)};b=l("<div/>").addClass(a.oClasses.sPaging+
b)[0];var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(f){if(d){var g=f._iDisplayStart,k=f._iDisplayLength,m=f.fnRecordsDisplay(),n=-1===k;g=n?0:Math.ceil(g/k);k=n?1:Math.ceil(m/k);m=c(g,k);var p;n=0;for(p=h.p.length;n<p;n++)lb(f,"pageButton")(f,h.p[n],n,m,g,k)}else c.fnUpdate(f,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,h=a.fnRecordsDisplay();0===h||-1===e?d=0:"number"===typeof b?(d=b*
e,d>h&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<h&&(d+=e):"last"==b?d=Math.floor((h-1)/e)*e:ea(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(F(a,null,"page",[a]),c&&ja(a));return b}function Pb(a){return l("<div/>",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).append("<div><div></div><div></div><div></div><div></div></div>").insertBefore(a.nTable)[0]}function V(a,
b){a.oFeatures.bProcessing&&l(a.aanFeatures.r).css("display",b?"block":"none");F(a,null,"processing",[a,b])}function Qb(a){var b=l(a.nTable),c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,h=a.oClasses,f=b.children("caption"),g=f.length?f[0]._captionSide:null,k=l(b[0].cloneNode(!1)),m=l(b[0].cloneNode(!1)),n=b.children("tfoot");n.length||(n=null);k=l("<div/>",{"class":h.sScrollWrapper}).append(l("<div/>",{"class":h.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,
width:d?d?K(d):null:"100%"}).append(l("<div/>",{"class":h.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(k.removeAttr("id").css("margin-left",0).append("top"===g?f:null).append(b.children("thead"))))).append(l("<div/>",{"class":h.sScrollBody}).css({position:"relative",overflow:"auto",width:d?K(d):null}).append(b));n&&k.append(l("<div/>",{"class":h.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?K(d):null:"100%"}).append(l("<div/>",{"class":h.sScrollFootInner}).append(m.removeAttr("id").css("margin-left",
0).append("bottom"===g?f:null).append(b.children("tfoot")))));b=k.children();var p=b[0];h=b[1];var t=n?b[2]:null;if(d)l(h).on("scroll.DT",function(v){v=this.scrollLeft;p.scrollLeft=v;n&&(t.scrollLeft=v)});l(h).css("max-height",e);c.bCollapse||l(h).css("height",e);a.nScrollHead=p;a.nScrollBody=h;a.nScrollFoot=t;a.aoDrawCallback.push({fn:Ja,sName:"scrolling"});return k[0]}function Ja(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY;b=b.iBarWidth;var h=l(a.nScrollHead),f=h[0].style,g=h.children("div"),k=
g[0].style,m=g.children("table");g=a.nScrollBody;var n=l(g),p=g.style,t=l(a.nScrollFoot).children("div"),v=t.children("table"),x=l(a.nTHead),w=l(a.nTable),r=w[0],C=r.style,G=a.nTFoot?l(a.nTFoot):null,ba=a.oBrowser,L=ba.bScrollOversize;U(a.aoColumns,"nTh");var O=[],I=[],H=[],fa=[],Z,Ba=function(D){D=D.style;D.paddingTop="0";D.paddingBottom="0";D.borderTopWidth="0";D.borderBottomWidth="0";D.height=0};var X=g.scrollHeight>g.clientHeight;if(a.scrollBarVis!==X&&a.scrollBarVis!==q)a.scrollBarVis=X,sa(a);
else{a.scrollBarVis=X;w.children("thead, tfoot").remove();if(G){X=G.clone().prependTo(w);var ca=G.find("tr");var Ca=X.find("tr");X.find("[id]").removeAttr("id")}var Ua=x.clone().prependTo(w);x=x.find("tr");X=Ua.find("tr");Ua.find("th, td").removeAttr("tabindex");Ua.find("[id]").removeAttr("id");c||(p.width="100%",h[0].style.width="100%");l.each(Pa(a,Ua),function(D,W){Z=ta(a,D);W.style.width=a.aoColumns[Z].sWidth});G&&da(function(D){D.style.width=""},Ca);h=w.outerWidth();""===c?(C.width="100%",L&&
(w.find("tbody").height()>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(w.outerWidth()-b)),h=w.outerWidth()):""!==d&&(C.width=K(d),h=w.outerWidth());da(Ba,X);da(function(D){var W=y.getComputedStyle?y.getComputedStyle(D).width:K(l(D).width());H.push(D.innerHTML);O.push(W)},X);da(function(D,W){D.style.width=O[W]},x);l(X).css("height",0);G&&(da(Ba,Ca),da(function(D){fa.push(D.innerHTML);I.push(K(l(D).css("width")))},Ca),da(function(D,W){D.style.width=I[W]},ca),l(Ca).height(0));da(function(D,
W){D.innerHTML='<div class="dataTables_sizing">'+H[W]+"</div>";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=O[W]},X);G&&da(function(D,W){D.innerHTML='<div class="dataTables_sizing">'+fa[W]+"</div>";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=I[W]},Ca);Math.round(w.outerWidth())<Math.round(h)?(ca=g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y")?h+b:h,L&&(g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y"))&&
(C.width=K(ca-b)),""!==c&&""===d||ea(a,1,"Possible column misalignment",6)):ca="100%";p.width=K(ca);f.width=K(ca);G&&(a.nScrollFoot.style.width=K(ca));!e&&L&&(p.height=K(r.offsetHeight+b));c=w.outerWidth();m[0].style.width=K(c);k.width=K(c);d=w.height()>g.clientHeight||"scroll"==n.css("overflow-y");e="padding"+(ba.bScrollbarLeft?"Left":"Right");k[e]=d?b+"px":"0px";G&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");w.children("colgroup").insertBefore(w.children("thead"));
n.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(g.scrollTop=0)}}function da(a,b,c){for(var d=0,e=0,h=b.length,f,g;e<h;){f=b[e].firstChild;for(g=c?c[e].firstChild:null;f;)1===f.nodeType&&(c?a(f,g,d):a(f,d),d++),f=f.nextSibling,g=c?g.nextSibling:null;e++}}function db(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,h=d.sX,f=d.sXInner,g=c.length,k=Ka(a,"bVisible"),m=l("th",a.nTHead),n=b.getAttribute("width"),p=b.parentNode,t=!1,v,x=a.oBrowser;d=x.bScrollOversize;(v=b.style.width)&&-1!==
v.indexOf("%")&&(n=v);for(v=0;v<k.length;v++){var w=c[k[v]];null!==w.sWidth&&(w.sWidth=cc(w.sWidthOrig,p),t=!0)}if(d||!t&&!h&&!e&&g==na(a)&&g==m.length)for(v=0;v<g;v++)k=ta(a,v),null!==k&&(c[k].sWidth=K(m.eq(v).width()));else{g=l(b).clone().css("visibility","hidden").removeAttr("id");g.find("tbody tr").remove();var r=l("<tr/>").appendTo(g.find("tbody"));g.find("thead, tfoot").remove();g.append(l(a.nTHead).clone()).append(l(a.nTFoot).clone());g.find("tfoot th, tfoot td").css("width","");m=Pa(a,g.find("thead")[0]);
for(v=0;v<k.length;v++)w=c[k[v]],m[v].style.width=null!==w.sWidthOrig&&""!==w.sWidthOrig?K(w.sWidthOrig):"",w.sWidthOrig&&h&&l(m[v]).append(l("<div/>").css({width:w.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v=0;v<k.length;v++)t=k[v],w=c[t],l(dc(a,t)).clone(!1).append(w.sContentPadding).appendTo(r);l("[name]",g).removeAttr("name");w=l("<div/>").css(h||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(g).appendTo(p);h&&f?g.width(f):h?
(g.css("width","auto"),g.removeAttr("width"),g.width()<p.clientWidth&&n&&g.width(p.clientWidth)):e?g.width(p.clientWidth):n&&g.width(n);for(v=e=0;v<k.length;v++)p=l(m[v]),f=p.outerWidth()-p.width(),p=x.bBounding?Math.ceil(m[v].getBoundingClientRect().width):p.outerWidth(),e+=p,c[k[v]].sWidth=K(p-f);b.style.width=K(e);w.remove()}n&&(b.style.width=K(n));!n&&!h||a._reszEvt||(b=function(){l(y).on("resize.DT-"+a.sInstance,mb(function(){sa(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0)}function cc(a,b){if(!a)return 0;
a=l("<div/>").css("width",K(a)).appendTo(b||A.body);b=a[0].offsetWidth;a.remove();return b}function dc(a,b){var c=ec(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]:l("<td/>").html(T(a,c,b,"display"))[0]}function ec(a,b){for(var c,d=-1,e=-1,h=0,f=a.aoData.length;h<f;h++)c=T(a,h,b,"display")+"",c=c.replace(Cc,""),c=c.replace(/&nbsp;/g," "),c.length>d&&(d=c.length,e=h);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function oa(a){var b=
[],c=a.aoColumns;var d=a.aaSortingFixed;var e=l.isPlainObject(d);var h=[];var f=function(n){n.length&&!Array.isArray(n[0])?h.push(n):l.merge(h,n)};Array.isArray(d)&&f(d);e&&d.pre&&f(d.pre);f(a.aaSorting);e&&d.post&&f(d.post);for(a=0;a<h.length;a++){var g=h[a][0];f=c[g].aDataSort;d=0;for(e=f.length;d<e;d++){var k=f[d];var m=c[k].sType||"string";h[a]._idx===q&&(h[a]._idx=l.inArray(h[a][1],c[k].asSorting));b.push({src:g,col:k,dir:h[a][1],index:h[a]._idx,type:m,formatter:u.ext.type.order[m+"-pre"]})}}return b}
function Lb(a){var b,c=[],d=u.ext.type.order,e=a.aoData,h=0,f=a.aiDisplayMaster;eb(a);var g=oa(a);var k=0;for(b=g.length;k<b;k++){var m=g[k];m.formatter&&h++;fc(a,m.col)}if("ssp"!=Q(a)&&0!==g.length){k=0;for(b=f.length;k<b;k++)c[f[k]]=k;h===g.length?f.sort(function(n,p){var t,v=g.length,x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;t<v;t++){var r=g[t];var C=x[r.col];var G=w[r.col];C=C<G?-1:C>G?1:0;if(0!==C)return"asc"===r.dir?C:-C}C=c[n];G=c[p];return C<G?-1:C>G?1:0}):f.sort(function(n,p){var t,v=g.length,
x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;t<v;t++){var r=g[t];var C=x[r.col];var G=w[r.col];r=d[r.type+"-"+r.dir]||d["string-"+r.dir];C=r(C,G);if(0!==C)return C}C=c[n];G=c[p];return C<G?-1:C>G?1:0})}a.bSorted=!0}function gc(a){var b=a.aoColumns,c=oa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d<e;d++){var h=b[d];var f=h.asSorting;var g=h.ariaTitle||h.sTitle.replace(/<.*?>/g,"");var k=h.nTh;k.removeAttribute("aria-sort");h.bSortable&&(0<c.length&&c[0].col==d?(k.setAttribute("aria-sort","asc"==
c[0].dir?"ascending":"descending"),h=f[c[0].index+1]||f[0]):h=f[0],g+="asc"===h?a.sSortAscending:a.sSortDescending);k.setAttribute("aria-label",g)}}function rb(a,b,c,d){var e=a.aaSorting,h=a.aoColumns[b].asSorting,f=function(g,k){var m=g._idx;m===q&&(m=l.inArray(g[1],h));return m+1<h.length?m+1:k?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=l.inArray(b,U(e,"0")),-1!==c?(b=f(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=h[b],e[c]._idx=b)):
(e.push([b,h[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=f(e[0]),e.length=1,e[0][1]=h[b],e[0]._idx=b):(e.length=0,e.push([b,h[0]]),e[0]._idx=0);ka(a);"function"==typeof d&&d(a)}function kb(a,b,c,d){var e=a.aoColumns[c];sb(b,{},function(h){!1!==e.bSortable&&(a.oFeatures.bProcessing?(V(a,!0),setTimeout(function(){rb(a,c,h.shiftKey,d);"ssp"!==Q(a)&&V(a,!1)},0)):rb(a,c,h.shiftKey,d))})}function Va(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=oa(a),e=a.oFeatures,h;if(e.bSort&&e.bSortClasses){e=
0;for(h=b.length;e<h;e++){var f=b[e].src;l(U(a.aoData,"anCells",f)).removeClass(c+(2>e?e+1:3))}e=0;for(h=d.length;e<h;e++)f=d[e].src,l(U(a.aoData,"anCells",f)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function fc(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ua(a,b)));for(var h,f=u.ext.type.order[c.sType+"-pre"],g=0,k=a.aoData.length;g<k;g++)if(c=a.aoData[g],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)h=d?e[g]:T(a,g,b,"sort"),c._aSortData[b]=f?f(h):h}
function Da(a){if(!a._bLoadingState){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:l.extend(!0,[],a.aaSorting),search:Zb(a.oPreviousSearch),columns:l.map(a.aoColumns,function(c,d){return{visible:c.bVisible,search:Zb(a.aoPreSearchCols[d])}})};a.oSavedState=b;F(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oFeatures.bStateSave&&!a.bDestroying&&a.fnStateSaveCallback.call(a.oInstance,a,b)}}function hc(a,b,c){if(a.oFeatures.bStateSave)return b=a.fnStateLoadCallback.call(a.oInstance,
a,function(d){tb(a,d,c)}),b!==q&&tb(a,b,c),!0;c()}function tb(a,b,c){var d,e=a.aoColumns;a._bLoadingState=!0;var h=a._bInitComplete?new u.Api(a):null;if(b&&b.time){var f=F(a,"aoStateLoadParams","stateLoadParams",[a,b]);if(-1!==l.inArray(!1,f))a._bLoadingState=!1;else if(f=a.iStateDuration,0<f&&b.time<+new Date-1E3*f)a._bLoadingState=!1;else if(b.columns&&e.length!==b.columns.length)a._bLoadingState=!1;else{a.oLoadedState=l.extend(!0,{},b);b.length!==q&&(h?h.page.len(b.length):a._iDisplayLength=b.length);
b.start!==q&&(null===h?(a._iDisplayStart=b.start,a.iInitDisplayStart=b.start):Ta(a,b.start/a._iDisplayLength));b.order!==q&&(a.aaSorting=[],l.each(b.order,function(k,m){a.aaSorting.push(m[0]>=e.length?[0,m[1]]:m)}));b.search!==q&&l.extend(a.oPreviousSearch,$b(b.search));if(b.columns){f=0;for(d=b.columns.length;f<d;f++){var g=b.columns[f];g.visible!==q&&(h?h.column(f).visible(g.visible,!1):e[f].bVisible=g.visible);g.search!==q&&l.extend(a.aoPreSearchCols[f],$b(g.search))}h&&h.columns.adjust()}a._bLoadingState=
!1;F(a,"aoStateLoaded","stateLoaded",[a,b])}}else a._bLoadingState=!1;c()}function Wa(a){var b=u.settings;a=l.inArray(a,U(b,"nTable"));return-1!==a?b[a]:null}function ea(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)y.console&&console.log&&console.log(c);else if(b=u.ext,b=b.sErrMode||b.errMode,a&&F(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==
typeof b&&b(a,d,c)}}function Y(a,b,c,d){Array.isArray(c)?l.each(c,function(e,h){Array.isArray(h)?Y(a,b,h[0],h[1]):Y(a,b,h)}):(d===q&&(d=c),b[c]!==q&&(a[d]=b[c]))}function ub(a,b,c){var d;for(d in b)if(b.hasOwnProperty(d)){var e=b[d];l.isPlainObject(e)?(l.isPlainObject(a[d])||(a[d]={}),l.extend(!0,a[d],e)):c&&"data"!==d&&"aaData"!==d&&Array.isArray(e)?a[d]=e.slice():a[d]=e}return a}function sb(a,b,c){l(a).on("click.DT",b,function(d){l(a).trigger("blur");c(d)}).on("keypress.DT",b,function(d){13===d.which&&
(d.preventDefault(),c(d))}).on("selectstart.DT",function(){return!1})}function R(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function F(a,b,c,d){var e=[];b&&(e=l.map(a[b].slice().reverse(),function(h,f){return h.fn.apply(a.oInstance,d)}));null!==c&&(b=l.Event(c+".dt"),l(a.nTable).trigger(b,d),e.push(b.result));return e}function qb(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function lb(a,b){a=a.renderer;var c=u.ext.renderer[b];
return l.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function Q(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Ea(a,b){var c=ic.numbers_length,d=Math.floor(c/2);b<=c?a=pa(0,b):a<=d?(a=pa(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=pa(b-(c-2),b):(a=pa(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function bb(a){l.each({num:function(b){return Xa(b,a)},"num-fmt":function(b){return Xa(b,
a,vb)},"html-num":function(b){return Xa(b,a,Ya)},"html-num-fmt":function(b){return Xa(b,a,Ya,vb)}},function(b,c){M.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(M.type.search[b+a]=M.type.search.html)})}function jc(a,b,c,d,e){return y.moment?a[b](e):y.luxon?a[c](e):d?a[d](e):a}function Za(a,b,c){if(y.moment){var d=y.moment.utc(a,b,c,!0);if(!d.isValid())return null}else if(y.luxon){d=b?y.luxon.DateTime.fromFormat(a,b):y.luxon.DateTime.fromISO(a);if(!d.isValid)return null;d.setLocale(c)}else b?(kc||
alert("DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17"),kc=!0):d=new Date(a);return d}function wb(a){return function(b,c,d,e){0===arguments.length?(d="en",b=c=null):1===arguments.length?(d="en",c=b,b=null):2===arguments.length&&(d=c,c=b,b=null);var h="datetime-"+c;u.ext.type.order[h]||(u.ext.type.detect.unshift(function(f){return f===h?h:!1}),u.ext.type.order[h+"-asc"]=function(f,g){f=f.valueOf();g=g.valueOf();return f===g?0:f<g?-1:1},u.ext.type.order[h+
"-desc"]=function(f,g){f=f.valueOf();g=g.valueOf();return f===g?0:f>g?-1:1});return function(f,g){if(null===f||f===q)"--now"===e?(f=new Date,f=new Date(Date.UTC(f.getFullYear(),f.getMonth(),f.getDate(),f.getHours(),f.getMinutes(),f.getSeconds()))):f="";if("type"===g)return h;if(""===f)return"sort"!==g?"":Za("0000-01-01 00:00:00",null,d);if(null!==c&&b===c&&"sort"!==g&&"type"!==g&&!(f instanceof Date))return f;var k=Za(f,b,d);if(null===k)return f;if("sort"===g)return k;f=null===c?jc(k,"toDate","toJSDate",
"")[a]():jc(k,"format","toFormat","toISOString",c);return"display"===g?$a(f):f}}}function lc(a){return function(){var b=[Wa(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a,b){if(this instanceof u)return l(a).DataTable(b);b=a;this.$=function(f,g){return this.api(!0).$(f,g)};this._=function(f,g){return this.api(!0).rows(f,g).data()};this.api=function(f){return f?new B(Wa(this[M.iApiIndex])):new B(this)};this.fnAddData=function(f,
g){var k=this.api(!0);f=Array.isArray(f)&&(Array.isArray(f[0])||l.isPlainObject(f[0]))?k.rows.add(f):k.row.add(f);(g===q||g)&&k.draw();return f.flatten().toArray()};this.fnAdjustColumnSizing=function(f){var g=this.api(!0).columns.adjust(),k=g.settings()[0],m=k.oScroll;f===q||f?g.draw(!1):(""!==m.sX||""!==m.sY)&&Ja(k)};this.fnClearTable=function(f){var g=this.api(!0).clear();(f===q||f)&&g.draw()};this.fnClose=function(f){this.api(!0).row(f).child.hide()};this.fnDeleteRow=function(f,g,k){var m=this.api(!0);
f=m.rows(f);var n=f.settings()[0],p=n.aoData[f[0][0]];f.remove();g&&g.call(this,n,p);(k===q||k)&&m.draw();return p};this.fnDestroy=function(f){this.api(!0).destroy(f)};this.fnDraw=function(f){this.api(!0).draw(f)};this.fnFilter=function(f,g,k,m,n,p){n=this.api(!0);null===g||g===q?n.search(f,k,m,p):n.column(g).search(f,k,m,p);n.draw()};this.fnGetData=function(f,g){var k=this.api(!0);if(f!==q){var m=f.nodeName?f.nodeName.toLowerCase():"";return g!==q||"td"==m||"th"==m?k.cell(f,g).data():k.row(f).data()||
null}return k.data().toArray()};this.fnGetNodes=function(f){var g=this.api(!0);return f!==q?g.row(f).node():g.rows().nodes().flatten().toArray()};this.fnGetPosition=function(f){var g=this.api(!0),k=f.nodeName.toUpperCase();return"TR"==k?g.row(f).index():"TD"==k||"TH"==k?(f=g.cell(f).index(),[f.row,f.columnVisible,f.column]):null};this.fnIsOpen=function(f){return this.api(!0).row(f).child.isShown()};this.fnOpen=function(f,g,k){return this.api(!0).row(f).child(g,k).show().child()[0]};this.fnPageChange=
function(f,g){f=this.api(!0).page(f);(g===q||g)&&f.draw(!1)};this.fnSetColumnVis=function(f,g,k){f=this.api(!0).column(f).visible(g);(k===q||k)&&f.columns.adjust().draw()};this.fnSettings=function(){return Wa(this[M.iApiIndex])};this.fnSort=function(f){this.api(!0).order(f).draw()};this.fnSortListener=function(f,g,k){this.api(!0).order.listener(f,g,k)};this.fnUpdate=function(f,g,k,m,n){var p=this.api(!0);k===q||null===k?p.row(g).data(f):p.cell(g,k).data(f);(n===q||n)&&p.columns.adjust();(m===q||m)&&
p.draw();return 0};this.fnVersionCheck=M.fnVersionCheck;var c=this,d=b===q,e=this.length;d&&(b={});this.oApi=this.internal=M.internal;for(var h in u.ext.internal)h&&(this[h]=lc(h));this.each(function(){var f={},g=1<e?ub(f,b,!0):b,k=0,m;f=this.getAttribute("id");var n=!1,p=u.defaults,t=l(this);if("table"!=this.nodeName.toLowerCase())ea(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{Db(p);Eb(p.column);P(p,p,!0);P(p.column,p.column,!0);P(p,l.extend(g,t.data()),!0);var v=u.settings;
k=0;for(m=v.length;k<m;k++){var x=v[k];if(x.nTable==this||x.nTHead&&x.nTHead.parentNode==this||x.nTFoot&&x.nTFoot.parentNode==this){var w=g.bRetrieve!==q?g.bRetrieve:p.bRetrieve;if(d||w)return x.oInstance;if(g.bDestroy!==q?g.bDestroy:p.bDestroy){x.oInstance.fnDestroy();break}else{ea(x,0,"Cannot reinitialise DataTable",3);return}}if(x.sTableId==this.id){v.splice(k,1);break}}if(null===f||""===f)this.id=f="DataTables_Table_"+u.ext._unique++;var r=l.extend(!0,{},u.models.oSettings,{sDestroyWidth:t[0].style.width,
sInstance:f,sTableId:f});r.nTable=this;r.oApi=c.internal;r.oInit=g;v.push(r);r.oInstance=1===c.length?c:t.dataTable();Db(g);la(g.oLanguage);g.aLengthMenu&&!g.iDisplayLength&&(g.iDisplayLength=Array.isArray(g.aLengthMenu[0])?g.aLengthMenu[0][0]:g.aLengthMenu[0]);g=ub(l.extend(!0,{},p),g);Y(r.oFeatures,g,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));Y(r,g,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod",
"aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"]]);Y(r.oScroll,g,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);Y(r.oLanguage,g,"fnInfoCallback");
R(r,"aoDrawCallback",g.fnDrawCallback,"user");R(r,"aoServerParams",g.fnServerParams,"user");R(r,"aoStateSaveParams",g.fnStateSaveParams,"user");R(r,"aoStateLoadParams",g.fnStateLoadParams,"user");R(r,"aoStateLoaded",g.fnStateLoaded,"user");R(r,"aoRowCallback",g.fnRowCallback,"user");R(r,"aoRowCreatedCallback",g.fnCreatedRow,"user");R(r,"aoHeaderCallback",g.fnHeaderCallback,"user");R(r,"aoFooterCallback",g.fnFooterCallback,"user");R(r,"aoInitComplete",g.fnInitComplete,"user");R(r,"aoPreDrawCallback",
g.fnPreDrawCallback,"user");r.rowIdFn=ma(g.rowId);Fb(r);var C=r.oClasses;l.extend(C,u.ext.classes,g.oClasses);t.addClass(C.sTable);r.iInitDisplayStart===q&&(r.iInitDisplayStart=g.iDisplayStart,r._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(r.bDeferLoading=!0,f=Array.isArray(g.iDeferLoading),r._iRecordsDisplay=f?g.iDeferLoading[0]:g.iDeferLoading,r._iRecordsTotal=f?g.iDeferLoading[1]:g.iDeferLoading);var G=r.oLanguage;l.extend(!0,G,g.oLanguage);G.sUrl?(l.ajax({dataType:"json",url:G.sUrl,
success:function(I){P(p.oLanguage,I);la(I);l.extend(!0,G,I,r.oInit.oLanguage);F(r,null,"i18n",[r]);Aa(r)},error:function(){Aa(r)}}),n=!0):F(r,null,"i18n",[r]);null===g.asStripeClasses&&(r.asStripeClasses=[C.sStripeOdd,C.sStripeEven]);f=r.asStripeClasses;var ba=t.children("tbody").find("tr").eq(0);-1!==l.inArray(!0,l.map(f,function(I,H){return ba.hasClass(I)}))&&(l("tbody tr",this).removeClass(f.join(" ")),r.asDestroyStripes=f.slice());f=[];v=this.getElementsByTagName("thead");0!==v.length&&(wa(r.aoHeader,
v[0]),f=Pa(r));if(null===g.aoColumns)for(v=[],k=0,m=f.length;k<m;k++)v.push(null);else v=g.aoColumns;k=0;for(m=v.length;k<m;k++)cb(r,f?f[k]:null);Hb(r,g.aoColumnDefs,v,function(I,H){Ia(r,I,H)});if(ba.length){var L=function(I,H){return null!==I.getAttribute("data-"+H)?H:null};l(ba[0]).children("th, td").each(function(I,H){var fa=r.aoColumns[I];if(fa.mData===I){var Z=L(H,"sort")||L(H,"order");H=L(H,"filter")||L(H,"search");if(null!==Z||null!==H)fa.mData={_:I+".display",sort:null!==Z?I+".@data-"+Z:q,
type:null!==Z?I+".@data-"+Z:q,filter:null!==H?I+".@data-"+H:q},Ia(r,I)}})}var O=r.oFeatures;f=function(){if(g.aaSorting===q){var I=r.aaSorting;k=0;for(m=I.length;k<m;k++)I[k][1]=r.aoColumns[k].asSorting[0]}Va(r);O.bSort&&R(r,"aoDrawCallback",function(){if(r.bSorted){var Z=oa(r),Ba={};l.each(Z,function(X,ca){Ba[ca.src]=ca.dir});F(r,null,"order",[r,Z,Ba]);gc(r)}});R(r,"aoDrawCallback",function(){(r.bSorted||"ssp"===Q(r)||O.bDeferRender)&&Va(r)},"sc");I=t.children("caption").each(function(){this._captionSide=
l(this).css("caption-side")});var H=t.children("thead");0===H.length&&(H=l("<thead/>").appendTo(t));r.nTHead=H[0];var fa=t.children("tbody");0===fa.length&&(fa=l("<tbody/>").insertAfter(H));r.nTBody=fa[0];H=t.children("tfoot");0===H.length&&0<I.length&&(""!==r.oScroll.sX||""!==r.oScroll.sY)&&(H=l("<tfoot/>").appendTo(t));0===H.length||0===H.children().length?t.addClass(C.sNoFooter):0<H.length&&(r.nTFoot=H[0],wa(r.aoFooter,r.nTFoot));if(g.aaData)for(k=0;k<g.aaData.length;k++)ia(r,g.aaData[k]);else(r.bDeferLoading||
"dom"==Q(r))&&La(r,l(r.nTBody).children("tr"));r.aiDisplay=r.aiDisplayMaster.slice();r.bInitialised=!0;!1===n&&Aa(r)};R(r,"aoDrawCallback",Da,"state_save");g.bStateSave?(O.bStateSave=!0,hc(r,g,f)):f()}});c=null;return this},M,z,J,xb={},mc=/[\r\n\u2028]/g,Ya=/<.*?>/g,Dc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,Ec=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,vb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,aa=function(a){return a&&!0!==a&&"-"!==
a?!1:!0},nc=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},oc=function(a,b){xb[b]||(xb[b]=new RegExp(ob(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(xb[b],"."):a},yb=function(a,b,c){var d="string"===typeof a;if(aa(a))return!0;b&&d&&(a=oc(a,b));c&&d&&(a=a.replace(vb,""));return!isNaN(parseFloat(a))&&isFinite(a)},pc=function(a,b,c){return aa(a)?!0:aa(a)||"string"===typeof a?yb(a.replace(Ya,""),b,c)?!0:null:null},U=function(a,b,c){var d=[],e=0,h=a.length;
if(c!==q)for(;e<h;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<h;e++)a[e]&&d.push(a[e][b]);return d},Fa=function(a,b,c,d){var e=[],h=0,f=b.length;if(d!==q)for(;h<f;h++)a[b[h]][c]&&e.push(a[b[h]][c][d]);else for(;h<f;h++)e.push(a[b[h]][c]);return e},pa=function(a,b){var c=[];if(b===q){b=0;var d=a}else d=b,b=a;for(a=b;a<d;a++)c.push(a);return c},qc=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},Oa=function(a){a:{if(!(2>a.length)){var b=a.slice().sort();for(var c=b[0],
d=1,e=b.length;d<e;d++){if(b[d]===c){b=!1;break a}c=b[d]}}b=!0}if(b)return a.slice();b=[];e=a.length;var h,f=0;d=0;a:for(;d<e;d++){c=a[d];for(h=0;h<f;h++)if(b[h]===c)continue a;b.push(c);f++}return b},rc=function(a,b){if(Array.isArray(b))for(var c=0;c<b.length;c++)rc(a,b[c]);else a.push(b);return a},sc=function(a,b){b===q&&(b=0);return-1!==this.indexOf(a,b)};Array.isArray||(Array.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)});Array.prototype.includes||(Array.prototype.includes=
sc);String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")});String.prototype.includes||(String.prototype.includes=sc);u.util={throttle:function(a,b){var c=b!==q?b:200,d,e;return function(){var h=this,f=+new Date,g=arguments;d&&f<d+c?(clearTimeout(e),e=setTimeout(function(){d=q;a.apply(h,g)},c)):(d=f,a.apply(h,g))}},escapeRegex:function(a){return a.replace(Ec,"\\$1")},set:function(a){if(l.isPlainObject(a))return u.util.set(a._);if(null===
a)return function(){};if("function"===typeof a)return function(c,d,e){a(c,"set",d,e)};if("string"!==typeof a||-1===a.indexOf(".")&&-1===a.indexOf("[")&&-1===a.indexOf("("))return function(c,d){c[a]=d};var b=function(c,d,e){e=hb(e);var h=e[e.length-1];for(var f,g,k=0,m=e.length-1;k<m;k++){if("__proto__"===e[k]||"constructor"===e[k])throw Error("Cannot set prototype values");f=e[k].match(Ga);g=e[k].match(qa);if(f){e[k]=e[k].replace(Ga,"");c[e[k]]=[];h=e.slice();h.splice(0,k+1);f=h.join(".");if(Array.isArray(d))for(g=
0,m=d.length;g<m;g++)h={},b(h,d[g],f),c[e[k]].push(h);else c[e[k]]=d;return}g&&(e[k]=e[k].replace(qa,""),c=c[e[k]](d));if(null===c[e[k]]||c[e[k]]===q)c[e[k]]={};c=c[e[k]]}if(h.match(qa))c[h.replace(qa,"")](d);else c[h.replace(Ga,"")]=d};return function(c,d){return b(c,d,a)}},get:function(a){if(l.isPlainObject(a)){var b={};l.each(a,function(d,e){e&&(b[d]=u.util.get(e))});return function(d,e,h,f){var g=b[e]||b._;return g!==q?g(d,e,h,f):d}}if(null===a)return function(d){return d};if("function"===typeof a)return function(d,
e,h,f){return a(d,e,h,f)};if("string"!==typeof a||-1===a.indexOf(".")&&-1===a.indexOf("[")&&-1===a.indexOf("("))return function(d,e){return d[a]};var c=function(d,e,h){if(""!==h){var f=hb(h);for(var g=0,k=f.length;g<k;g++){h=f[g].match(Ga);var m=f[g].match(qa);if(h){f[g]=f[g].replace(Ga,"");""!==f[g]&&(d=d[f[g]]);m=[];f.splice(0,g+1);f=f.join(".");if(Array.isArray(d))for(g=0,k=d.length;g<k;g++)m.push(c(d[g],e,f));d=h[0].substring(1,h[0].length-1);d=""===d?m:m.join(d);break}else if(m){f[g]=f[g].replace(qa,
"");d=d[f[g]]();continue}if(null===d||d[f[g]]===q)return q;d=d[f[g]]}}return d};return function(d,e){return c(d,e,a)}}};var S=function(a,b,c){a[b]!==q&&(a[c]=a[b])},Ga=/\[.*?\]$/,qa=/\(\)$/,ma=u.util.get,ha=u.util.set,ob=u.util.escapeRegex,Sa=l("<div>")[0],Bc=Sa.textContent!==q,Cc=/<.*?>/g,mb=u.util.throttle,tc=[],N=Array.prototype,Fc=function(a){var b,c=u.settings,d=l.map(c,function(h,f){return h.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e=
l.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=l(a):a instanceof l&&(b=a)}else return[];if(b)return b.map(function(h){e=l.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var B=function(a,b){if(!(this instanceof B))return new B(a,b);var c=[],d=function(f){(f=Fc(f))&&c.push.apply(c,f)};if(Array.isArray(a))for(var e=0,h=a.length;e<h;e++)d(a[e]);else d(a);this.context=Oa(c);b&&l.merge(this,b);this.selector={rows:null,
cols:null,opts:null};B.extend(this,this,tc)};u.Api=B;l.extend(B.prototype,{any:function(){return 0!==this.count()},concat:N.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new B(b[a],this[a]):null},filter:function(a){var b=[];if(N.filter)b=N.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);
return new B(this.context,b)},flatten:function(){var a=[];return new B(this.context,a.concat.apply(a,this.toArray()))},join:N.join,indexOf:N.indexOf||function(a,b){b=b||0;for(var c=this.length;b<c;b++)if(this[b]===a)return b;return-1},iterator:function(a,b,c,d){var e=[],h,f,g=this.context,k,m=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);var n=0;for(h=g.length;n<h;n++){var p=new B(g[n]);if("table"===b){var t=c.call(p,g[n],n);t!==q&&e.push(t)}else if("columns"===b||"rows"===b)t=c.call(p,g[n],
this[n],n),t!==q&&e.push(t);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){var v=this[n];"column-rows"===b&&(k=ab(g[n],m.opts));var x=0;for(f=v.length;x<f;x++)t=v[x],t="cell"===b?c.call(p,g[n],t.row,t.column,n,x):c.call(p,g[n],t,n,x,k),t!==q&&e.push(t)}}return e.length||d?(a=new B(g,a?e.concat.apply([],e):e),b=a.selector,b.rows=m.rows,b.cols=m.cols,b.opts=m.opts,a):this},lastIndexOf:N.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,
map:function(a){var b=[];if(N.map)b=N.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new B(this.context,b)},pluck:function(a){var b=u.util.get(a);return this.map(function(c){return b(c)})},pop:N.pop,push:N.push,reduce:N.reduce||function(a,b){return Gb(this,a,b,0,this.length,1)},reduceRight:N.reduceRight||function(a,b){return Gb(this,a,b,this.length-1,-1,-1)},reverse:N.reverse,selector:null,shift:N.shift,slice:function(){return new B(this.context,
this)},sort:N.sort,splice:N.splice,toArray:function(){return N.slice.call(this)},to$:function(){return l(this)},toJQuery:function(){return l(this)},unique:function(){return new B(this.context,Oa(this))},unshift:N.unshift});B.extend=function(a,b,c){if(c.length&&b&&(b instanceof B||b.__dt_wrapper)){var d,e=function(g,k,m){return function(){var n=k.apply(g,arguments);B.extend(n,n,m.methodExt);return n}};var h=0;for(d=c.length;h<d;h++){var f=c[h];b[f.name]="function"===f.type?e(a,f.val,f):"object"===
f.type?{}:f.val;b[f.name].__dt_wrapper=!0;B.extend(a,b[f.name],f.propExt)}}};B.register=z=function(a,b){if(Array.isArray(a))for(var c=0,d=a.length;c<d;c++)B.register(a[c],b);else{d=a.split(".");var e=tc,h;a=0;for(c=d.length;a<c;a++){var f=(h=-1!==d[a].indexOf("()"))?d[a].replace("()",""):d[a];a:{var g=0;for(var k=e.length;g<k;g++)if(e[g].name===f){g=e[g];break a}g=null}g||(g={name:f,val:{},methodExt:[],propExt:[],type:"object"},e.push(g));a===c-1?(g.val=b,g.type="function"===typeof b?"function":l.isPlainObject(b)?
"object":"other"):e=h?g.methodExt:g.propExt}}};B.registerPlural=J=function(a,b,c){B.register(a,c);B.register(b,function(){var d=c.apply(this,arguments);return d===this?this:d instanceof B?d.length?Array.isArray(d[0])?new B(d.context,d[0]):d[0]:q:d})};var uc=function(a,b){if(Array.isArray(a))return l.map(a,function(d){return uc(d,b)});if("number"===typeof a)return[b[a]];var c=l.map(b,function(d,e){return d.nTable});return l(c).filter(a).map(function(d){d=l.inArray(this,c);return b[d]}).toArray()};
z("tables()",function(a){return a!==q&&null!==a?new B(uc(a,this.context)):this});z("table()",function(a){a=this.tables(a);var b=a.context;return b.length?new B(b[0]):a});J("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});J("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});J("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},
1)});J("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});J("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});z("draw()",function(a){return this.iterator("table",function(b){"page"===a?ja(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),ka(b,!1===a))})});z("page()",function(a){return a===q?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});z("page.info()",
function(a){if(0===this.context.length)return q;a=this.context[0];var b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===Q(a)}});z("page.len()",function(a){return a===q?0!==this.context.length?this.context[0]._iDisplayLength:q:this.iterator("table",function(b){pb(b,a)})});var vc=function(a,b,
c){if(c){var d=new B(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==Q(a))ka(a,b);else{V(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();Qa(a,[],function(h){Ma(a);h=za(a,h);for(var f=0,g=h.length;f<g;f++)ia(a,h[f]);ka(a,b);V(a,!1)})}};z("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});z("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});z("ajax.reload()",function(a,b){return this.iterator("table",function(c){vc(c,!1===b,a)})});
z("ajax.url()",function(a){var b=this.context;if(a===q){if(0===b.length)return q;b=b[0];return b.ajax?l.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(c){l.isPlainObject(c.ajax)?c.ajax.url=a:c.ajax=a})});z("ajax.url().load()",function(a,b){return this.iterator("table",function(c){vc(c,!1===b,a)})});var zb=function(a,b,c,d,e){var h=[],f,g,k;var m=typeof b;b&&"string"!==m&&"function"!==m&&b.length!==q||(b=[b]);m=0;for(g=b.length;m<g;m++){var n=b[m]&&b[m].split&&
!b[m].match(/[\[\(:]/)?b[m].split(","):[b[m]];var p=0;for(k=n.length;p<k;p++)(f=c("string"===typeof n[p]?n[p].trim():n[p]))&&f.length&&(h=h.concat(f))}a=M.selector[a];if(a.length)for(m=0,g=a.length;m<g;m++)h=a[m](d,e,h);return Oa(h)},Ab=function(a){a||(a={});a.filter&&a.search===q&&(a.search=a.filter);return l.extend({search:"none",order:"current",page:"all"},a)},Bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=
0;return a},ab=function(a,b){var c=[],d=a.aiDisplay;var e=a.aiDisplayMaster;var h=b.search;var f=b.order;b=b.page;if("ssp"==Q(a))return"removed"===h?[]:pa(0,e.length);if("current"==b)for(f=a._iDisplayStart,a=a.fnDisplayEnd();f<a;f++)c.push(d[f]);else if("current"==f||"applied"==f)if("none"==h)c=e.slice();else if("applied"==h)c=d.slice();else{if("removed"==h){var g={};f=0;for(a=d.length;f<a;f++)g[d[f]]=null;c=l.map(e,function(k){return g.hasOwnProperty(k)?null:k})}}else if("index"==f||"original"==
f)for(f=0,a=a.aoData.length;f<a;f++)"none"==h?c.push(f):(e=l.inArray(f,d),(-1===e&&"removed"==h||0<=e&&"applied"==h)&&c.push(f));return c},Gc=function(a,b,c){var d;return zb("row",b,function(e){var h=nc(e),f=a.aoData;if(null!==h&&!c)return[h];d||(d=ab(a,c));if(null!==h&&-1!==l.inArray(h,d))return[h];if(null===e||e===q||""===e)return d;if("function"===typeof e)return l.map(d,function(k){var m=f[k];return e(k,m._aData,m.nTr)?k:null});if(e.nodeName){h=e._DT_RowIndex;var g=e._DT_CellIndex;if(h!==q)return f[h]&&
f[h].nTr===e?[h]:[];if(g)return f[g.row]&&f[g.row].nTr===e.parentNode?[g.row]:[];h=l(e).closest("*[data-dt-row]");return h.length?[h.data("dt-row")]:[]}if("string"===typeof e&&"#"===e.charAt(0)&&(h=a.aIds[e.replace(/^#/,"")],h!==q))return[h.idx];h=qc(Fa(a.aoData,d,"nTr"));return l(h).filter(e).map(function(){return this._DT_RowIndex}).toArray()},a,c)};z("rows()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=Ab(b);var c=this.iterator("table",function(d){return Gc(d,a,b)},1);c.selector.rows=
a;c.selector.opts=b;return c});z("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||q},1)});z("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return Fa(a.aoData,b,"_aData")},1)});J("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){b=b.aoData[c];return"search"===a?b._aFilterData:b._aSortData},1)});J("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){va(b,
c,a)})});J("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});J("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var h=0,f=this[d].length;h<f;h++){var g=c[d].rowIdFn(c[d].aoData[this[d][h]]._aData);b.push((!0===a?"#":"")+g)}return new B(c,b)});J("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,h=e[c],f,g;e.splice(c,1);var k=0;for(f=e.length;k<
f;k++){var m=e[k];var n=m.anCells;null!==m.nTr&&(m.nTr._DT_RowIndex=k);if(null!==n)for(m=0,g=n.length;m<g;m++)n[m]._DT_CellIndex.row=k}Na(b.aiDisplayMaster,c);Na(b.aiDisplay,c);Na(a[d],c,!1);0<b._iRecordsDisplay&&b._iRecordsDisplay--;qb(b);c=b.rowIdFn(h._aData);c!==q&&delete b.aIds[c]});this.iterator("table",function(b){for(var c=0,d=b.aoData.length;c<d;c++)b.aoData[c].idx=c});return this});z("rows.add()",function(a){var b=this.iterator("table",function(d){var e,h=[];var f=0;for(e=a.length;f<e;f++){var g=
a[f];g.nodeName&&"TR"===g.nodeName.toUpperCase()?h.push(La(d,g)[0]):h.push(ia(d,g))}return h},1),c=this.rows(-1);c.pop();l.merge(c,b);return c});z("row()",function(a,b){return Bb(this.rows(a,b))});z("row().data()",function(a){var b=this.context;if(a===q)return b.length&&this.length?b[0].aoData[this[0]]._aData:q;var c=b[0].aoData[this[0]];c._aData=a;Array.isArray(a)&&c.nTr&&c.nTr.id&&ha(b[0].rowId)(a,c.nTr.id);va(b[0],this[0],"data");return this});z("row().node()",function(){var a=this.context;return a.length&&
this.length?a[0].aoData[this[0]].nTr||null:null});z("row.add()",function(a){a instanceof l&&a.length&&(a=a[0]);var b=this.iterator("table",function(c){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?La(c,a)[0]:ia(c,a)});return this.row(b[0])});l(A).on("plugin-init.dt",function(a,b){a=new B(b);a.on("stateSaveParams",function(d,e,h){d=e.rowIdFn;e=e.aoData;for(var f=[],g=0;g<e.length;g++)e[g]._detailsShow&&f.push("#"+d(e[g]._aData));h.childRows=f});var c=a.state.loaded();c&&c.childRows&&a.rows(l.map(c.childRows,
function(d){return d.replace(/:/g,"\\:")})).every(function(){F(b,null,"requestChild",[this])})});var Hc=function(a,b,c,d){var e=[],h=function(f,g){if(Array.isArray(f)||f instanceof l)for(var k=0,m=f.length;k<m;k++)h(f[k],g);else f.nodeName&&"tr"===f.nodeName.toLowerCase()?e.push(f):(k=l("<tr><td></td></tr>").addClass(g),l("td",k).addClass(g).html(f)[0].colSpan=na(a),e.push(k[0]))};h(c,d);b._details&&b._details.detach();b._details=l(e);b._detailsShow&&b._details.insertAfter(b.nTr)},wc=u.util.throttle(function(a){Da(a[0])},
500),Cb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details=q,l(a.nTr).removeClass("dt-hasChild"),wc(c))},xc=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];d._details&&((d._detailsShow=b)?(d._details.insertAfter(d.nTr),l(d.nTr).addClass("dt-hasChild")):(d._details.detach(),l(d.nTr).removeClass("dt-hasChild")),F(c[0],null,"childRow",[b,a.row(a[0])]),Ic(c[0]),wc(c))}},Ic=function(a){var b=new B(a),
c=a.aoData;b.off("draw.dt.DT_details column-sizing.dt.DT_details destroy.dt.DT_details");0<U(c,"_details").length&&(b.on("draw.dt.DT_details",function(d,e){a===e&&b.rows({page:"current"}).eq(0).each(function(h){h=c[h];h._detailsShow&&h._details.insertAfter(h.nTr)})}),b.on("column-sizing.dt.DT_details",function(d,e,h,f){if(a===e)for(e=na(e),h=0,f=c.length;h<f;h++)d=c[h],d._details&&d._details.children("td[colspan]").attr("colspan",e)}),b.on("destroy.dt.DT_details",function(d,e){if(a===e)for(d=0,e=
c.length;d<e;d++)c[d]._details&&Cb(b,d)}))};z("row().child()",function(a,b){var c=this.context;if(a===q)return c.length&&this.length?c[0].aoData[this[0]]._details:q;!0===a?this.child.show():!1===a?Cb(this):c.length&&this.length&&Hc(c[0],c[0].aoData[this[0]],a,b);return this});z(["row().child.show()","row().child().show()"],function(a){xc(this,!0);return this});z(["row().child.hide()","row().child().hide()"],function(){xc(this,!1);return this});z(["row().child.remove()","row().child().remove()"],function(){Cb(this);
return this});z("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var Jc=/^([^:]+):(name|visIdx|visible)$/,yc=function(a,b,c,d,e){c=[];d=0;for(var h=e.length;d<h;d++)c.push(T(a,e[d],b));return c},Kc=function(a,b,c){var d=a.aoColumns,e=U(d,"sName"),h=U(d,"nTh");return zb("column",b,function(f){var g=nc(f);if(""===f)return pa(d.length);if(null!==g)return[0<=g?g:d.length+g];if("function"===typeof f){var k=ab(a,c);return l.map(d,
function(p,t){return f(t,yc(a,t,0,0,k),h[t])?t:null})}var m="string"===typeof f?f.match(Jc):"";if(m)switch(m[2]){case "visIdx":case "visible":g=parseInt(m[1],10);if(0>g){var n=l.map(d,function(p,t){return p.bVisible?t:null});return[n[n.length+g]]}return[ta(a,g)];case "name":return l.map(e,function(p,t){return p===m[1]?t:null});default:return[]}if(f.nodeName&&f._DT_CellIndex)return[f._DT_CellIndex.column];g=l(h).filter(f).map(function(){return l.inArray(this,h)}).toArray();if(g.length||!f.nodeName)return g;
g=l(f).closest("*[data-dt-column]");return g.length?[g.data("dt-column")]:[]},a,c)};z("columns()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=Ab(b);var c=this.iterator("table",function(d){return Kc(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh},1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c,
d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",yc,1)});J("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,h){return Fa(b.aoData,h,"search"===a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",
function(a,b,c,d,e){return Fa(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(e,h){if(a===q)return e.aoColumns[h].bVisible;var f=e.aoColumns,g=f[h],k=e.aoData,m;if(a!==q&&g.bVisible!==a){if(a){var n=l.inArray(!0,U(f,"bVisible"),h+1);f=0;for(m=k.length;f<m;f++){var p=k[f].nTr;e=k[f].anCells;p&&p.insertBefore(e[h],e[n]||null)}}else l(U(e.aoData,"anCells",h)).detach();g.bVisible=a}});a!==q&&this.iterator("table",function(e){xa(e,
e.aoHeader);xa(e,e.aoFooter);e.aiDisplay.length||l(e.nTBody).find("td[colspan]").attr("colspan",na(e));Da(e);c.iterator("column",function(h,f){F(h,null,"column-visibility",[h,f,a,b])});(b===q||b)&&c.columns.adjust()});return d});J("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?ua(b,c):c},1)});z("columns.adjust()",function(){return this.iterator("table",function(a){sa(a)},1)});z("column.index()",function(a,b){if(0!==this.context.length){var c=
this.context[0];if("fromVisible"===a||"toData"===a)return ta(c,b);if("fromData"===a||"toVisible"===a)return ua(c,b)}});z("column()",function(a,b){return Bb(this.columns(a,b))});var Lc=function(a,b,c){var d=a.aoData,e=ab(a,c),h=qc(Fa(d,e,"anCells")),f=l(rc([],h)),g,k=a.aoColumns.length,m,n,p,t,v,x;return zb("cell",b,function(w){var r="function"===typeof w;if(null===w||w===q||r){m=[];n=0;for(p=e.length;n<p;n++)for(g=e[n],t=0;t<k;t++)v={row:g,column:t},r?(x=d[g],w(v,T(a,g,t),x.anCells?x.anCells[t]:null)&&
m.push(v)):m.push(v);return m}if(l.isPlainObject(w))return w.column!==q&&w.row!==q&&-1!==l.inArray(w.row,e)?[w]:[];r=f.filter(w).map(function(C,G){return{row:G._DT_CellIndex.row,column:G._DT_CellIndex.column}}).toArray();if(r.length||!w.nodeName)return r;x=l(w).closest("*[data-dt-row]");return x.length?[{row:x.data("dt-row"),column:x.data("dt-column")}]:[]},a,c)};z("cells()",function(a,b,c){l.isPlainObject(a)&&(a.row===q?(c=a,a=null):(c=b,b=null));l.isPlainObject(b)&&(c=b,b=null);if(null===b||b===
q)return this.iterator("table",function(n){return Lc(n,a,Ab(c))});var d=c?{page:c.page,order:c.order,search:c.search}:{},e=this.columns(b,d),h=this.rows(a,d),f,g,k,m;d=this.iterator("table",function(n,p){n=[];f=0;for(g=h[p].length;f<g;f++)for(k=0,m=e[p].length;k<m;k++)n.push({row:h[p][f],column:e[p][k]});return n},1);d=c&&c.selected?this.cells(d,c):d;l.extend(d.selector,{cols:b,rows:a,opts:c});return d});J("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=
a.aoData[b])&&a.anCells?a.anCells[c]:q},1)});z("cells().data()",function(){return this.iterator("cell",function(a,b,c){return T(a,b,c)},1)});J("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});J("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return T(b,c,d,a)},1)});J("cells().indexes()","cell().index()",function(){return this.iterator("cell",
function(a,b,c){return{row:b,column:c,columnVisible:ua(a,c)}},1)});J("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){va(b,c,a,d)})});z("cell()",function(a,b,c){return Bb(this.cells(a,b,c))});z("cell().data()",function(a){var b=this.context,c=this[0];if(a===q)return b.length&&c.length?T(b[0],c[0].row,c[0].column):q;Ib(b[0],c[0].row,c[0].column,a);va(b[0],c[0].row,"data",c[0].column);return this});z("order()",function(a,b){var c=this.context;if(a===
q)return 0!==c.length?c[0].aaSorting:q;"number"===typeof a?a=[[a,b]]:a.length&&!Array.isArray(a[0])&&(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(d){d.aaSorting=a.slice()})});z("order.listener()",function(a,b,c){return this.iterator("table",function(d){kb(d,a,b,c)})});z("order.fixed()",function(a){if(!a){var b=this.context;b=b.length?b[0].aaSortingFixed:q;return Array.isArray(b)?{pre:b}:b}return this.iterator("table",function(c){c.aaSortingFixed=l.extend(!0,{},a)})});
z(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];l.each(b[d],function(h,f){e.push([f,a])});c.aaSorting=e})});z("search()",function(a,b,c,d){var e=this.context;return a===q?0!==e.length?e[0].oPreviousSearch.sSearch:q:this.iterator("table",function(h){h.oFeatures.bFilter&&ya(h,l.extend({},h.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});J("columns().search()","column().search()",
function(a,b,c,d){return this.iterator("column",function(e,h){var f=e.aoPreSearchCols;if(a===q)return f[h].sSearch;e.oFeatures.bFilter&&(l.extend(f[h],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),ya(e,e.oPreviousSearch,1))})});z("state()",function(){return this.context.length?this.context[0].oSavedState:null});z("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});z("state.loaded()",function(){return this.context.length?
this.context[0].oLoadedState:null});z("state.save()",function(){return this.iterator("table",function(a){Da(a)})});u.versionCheck=u.fnVersionCheck=function(a){var b=u.version.split(".");a=a.split(".");for(var c,d,e=0,h=a.length;e<h;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};u.isDataTable=u.fnIsDataTable=function(a){var b=l(a).get(0),c=!1;if(a instanceof u.Api)return!0;l.each(u.settings,function(d,e){d=e.nScrollHead?l("table",e.nScrollHead)[0]:null;var h=e.nScrollFoot?
l("table",e.nScrollFoot)[0]:null;if(e.nTable===b||d===b||h===b)c=!0});return c};u.tables=u.fnTables=function(a){var b=!1;l.isPlainObject(a)&&(b=a.api,a=a.visible);var c=l.map(u.settings,function(d){if(!a||a&&l(d.nTable).is(":visible"))return d.nTable});return b?new B(c):c};u.camelToHungarian=P;z("$()",function(a,b){b=this.rows(b).nodes();b=l(b);return l([].concat(b.filter(a).toArray(),b.find(a).toArray()))});l.each(["on","one","off"],function(a,b){z(b+"()",function(){var c=Array.prototype.slice.call(arguments);
c[0]=l.map(c[0].split(/\s/),function(e){return e.match(/\.dt\b/)?e:e+".dt"}).join(" ");var d=l(this.tables().nodes());d[b].apply(d,c);return this})});z("clear()",function(){return this.iterator("table",function(a){Ma(a)})});z("settings()",function(){return new B(this.context,this.context)});z("init()",function(){var a=this.context;return a.length?a[0].oInit:null});z("data()",function(){return this.iterator("table",function(a){return U(a.aoData,"_aData")}).flatten()});z("destroy()",function(a){a=a||
!1;return this.iterator("table",function(b){var c=b.oClasses,d=b.nTable,e=b.nTBody,h=b.nTHead,f=b.nTFoot,g=l(d);e=l(e);var k=l(b.nTableWrapper),m=l.map(b.aoData,function(p){return p.nTr}),n;b.bDestroying=!0;F(b,"aoDestroyCallback","destroy",[b]);a||(new B(b)).columns().visible(!0);k.off(".DT").find(":not(tbody *)").off(".DT");l(y).off(".DT-"+b.sInstance);d!=h.parentNode&&(g.children("thead").detach(),g.append(h));f&&d!=f.parentNode&&(g.children("tfoot").detach(),g.append(f));b.aaSorting=[];b.aaSortingFixed=
[];Va(b);l(m).removeClass(b.asStripeClasses.join(" "));l("th, td",h).removeClass(c.sSortable+" "+c.sSortableAsc+" "+c.sSortableDesc+" "+c.sSortableNone);e.children().detach();e.append(m);h=b.nTableWrapper.parentNode;f=a?"remove":"detach";g[f]();k[f]();!a&&h&&(h.insertBefore(d,b.nTableReinsertBefore),g.css("width",b.sDestroyWidth).removeClass(c.sTable),(n=b.asDestroyStripes.length)&&e.children().each(function(p){l(this).addClass(b.asDestroyStripes[p%n])}));c=l.inArray(b,u.settings);-1!==c&&u.settings.splice(c,
1)})});l.each(["column","row","cell"],function(a,b){z(b+"s().every()",function(c){var d=this.selector.opts,e=this;return this.iterator(b,function(h,f,g,k,m){c.call(e[b](f,"cell"===b?g:d,"cell"===b?d:q),f,g,k,m)})})});z("i18n()",function(a,b,c){var d=this.context[0];a=ma(a)(d.oLanguage);a===q&&(a=b);c!==q&&l.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)});u.version="1.12.1";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,"return":!1};
u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};u.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,
sWidth:null,sWidthOrig:null};u.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+
a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",
sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:l.extend({},u.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};E(u.defaults);
u.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};E(u.defaults.column);u.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,
bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],
aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:q,oAjaxData:q,fnServerData:null,aoServerParams:[],sServerMethod:null,
fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==Q(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==Q(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+
a,d=this.aiDisplay.length,e=this.oFeatures,h=e.bPaginate;return e.bServerSide?!1===h||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!h||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=M={buttons:{},classes:{},build:"dt/dt-1.12.1",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],
search:{},order:{}},_unique:0,fnVersionCheck:u.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:u.version};l.extend(M,{afnFiltering:M.search,aTypes:M.type.detect,ofnSearch:M.type.search,oSort:M.type.order,afnSortData:M.order,aoFeatures:M.feature,oApi:M.internal,oStdClasses:M.classes,oPagination:M.pager});l.extend(u.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",
sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",
sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ic=u.ext.pager;l.extend(ic,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Ea(a,b)]},simple_numbers:function(a,b){return["previous",
Ea(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Ea(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Ea(a,b),"last"]},_numbers:Ea,numbers_length:7});l.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,h){var f=a.oClasses,g=a.oLanguage.oPaginate,k=a.oLanguage.oAria.paginate||{},m,n,p=0,t=function(x,w){var r,C=f.sPageButtonDisabled,G=function(I){Ta(a,I.data.action,!0)};var ba=0;for(r=w.length;ba<r;ba++){var L=w[ba];if(Array.isArray(L)){var O=l("<"+(L.DT_el||
"div")+"/>").appendTo(x);t(O,L)}else{m=null;n=L;O=a.iTabIndex;switch(L){case "ellipsis":x.append('<span class="ellipsis">&#x2026;</span>');break;case "first":m=g.sFirst;0===e&&(O=-1,n+=" "+C);break;case "previous":m=g.sPrevious;0===e&&(O=-1,n+=" "+C);break;case "next":m=g.sNext;if(0===h||e===h-1)O=-1,n+=" "+C;break;case "last":m=g.sLast;if(0===h||e===h-1)O=-1,n+=" "+C;break;default:m=a.fnFormatNumber(L+1),n=e===L?f.sPageButtonActive:""}null!==m&&(O=l("<a>",{"class":f.sPageButton+" "+n,"aria-controls":a.sTableId,
"aria-label":k[L],"data-dt-idx":p,tabindex:O,id:0===c&&"string"===typeof L?a.sTableId+"_"+L:null}).html(m).appendTo(x),sb(O,{action:L},G),p++)}}};try{var v=l(b).find(A.activeElement).data("dt-idx")}catch(x){}t(l(b).empty(),d);v!==q&&l(b).find("[data-dt-idx="+v+"]").trigger("focus")}}});l.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return yb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!Dc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||aa(a)?"date":
null},function(a,b){b=b.oLanguage.sDecimal;return yb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return pc(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return pc(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return aa(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);l.extend(u.ext.type.search,{html:function(a){return aa(a)?a:"string"===typeof a?a.replace(mc," ").replace(Ya,""):""},string:function(a){return aa(a)?a:"string"===typeof a?a.replace(mc," "):
a}});var Xa=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=oc(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};l.extend(M.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return aa(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return aa(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,
b){return a<b?1:a>b?-1:0}});bb("");l.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c,d){l("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(l("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,
b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==g[e]?d.sSortJUIAsc:"desc"==g[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var $a=function(a){Array.isArray(a)&&(a=a.join(","));return"string"===typeof a?a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,
"&quot;"):a},kc=!1,zc=",",Ac=".";if(Intl)try{for(var Ha=(new Intl.NumberFormat).formatToParts(100000.1),ra=0;ra<Ha.length;ra++)"group"===Ha[ra].type?zc=Ha[ra].value:"decimal"===Ha[ra].type&&(Ac=Ha[ra].value)}catch(a){}u.datetime=function(a,b){var c="datetime-detect-"+a;b||(b="en");u.ext.type.order[c]||(u.ext.type.detect.unshift(function(d){var e=Za(d,a,b);return""===d||e?c:!1}),u.ext.type.order[c+"-pre"]=function(d){return Za(d,a,b)||0})};u.render={date:wb("toLocaleDateString"),datetime:wb("toLocaleString"),
time:wb("toLocaleTimeString"),number:function(a,b,c,d,e){if(null===a||a===q)a=zc;if(null===b||b===q)b=Ac;return{display:function(h){if("number"!==typeof h&&"string"!==typeof h||""===h||null===h)return h;var f=0>h?"-":"",g=parseFloat(h);if(isNaN(g))return $a(h);g=g.toFixed(c);h=Math.abs(g);g=parseInt(h,10);h=c?b+(h-g).toFixed(c).substring(2):"";0===g&&0===parseFloat(h)&&(f="");return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+h+(e||"")}}},text:function(){return{display:$a,filter:$a}}};
l.extend(u.ext.internal,{_fnExternApiFunc:lc,_fnBuildAjax:Qa,_fnAjaxUpdate:Kb,_fnAjaxParameters:Tb,_fnAjaxUpdateDraw:Ub,_fnAjaxDataSrc:za,_fnAddColumn:cb,_fnColumnOptions:Ia,_fnAdjustColumnSizing:sa,_fnVisibleToColumnIndex:ta,_fnColumnIndexToVisible:ua,_fnVisbleColumns:na,_fnGetColumns:Ka,_fnColumnTypes:eb,_fnApplyColumnDefs:Hb,_fnHungarianMap:E,_fnCamelToHungarian:P,_fnLanguageCompat:la,_fnBrowserDetect:Fb,_fnAddData:ia,_fnAddTr:La,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==q?b._DT_RowIndex:
null},_fnNodeToColumnIndex:function(a,b,c){return l.inArray(c,a.aoData[b].anCells)},_fnGetCellData:T,_fnSetCellData:Ib,_fnSplitObjNotation:hb,_fnGetObjectDataFn:ma,_fnSetObjectDataFn:ha,_fnGetDataMaster:ib,_fnClearTable:Ma,_fnDeleteIndex:Na,_fnInvalidate:va,_fnGetRowElements:gb,_fnCreateTr:fb,_fnBuildHead:Jb,_fnDrawHead:xa,_fnDraw:ja,_fnReDraw:ka,_fnAddOptionsHtml:Mb,_fnDetectHeader:wa,_fnGetUniqueThs:Pa,_fnFeatureHtmlFilter:Ob,_fnFilterComplete:ya,_fnFilterCustom:Xb,_fnFilterColumn:Wb,_fnFilter:Vb,
_fnFilterCreateSearch:nb,_fnEscapeRegex:ob,_fnFilterData:Yb,_fnFeatureHtmlInfo:Rb,_fnUpdateInfo:ac,_fnInfoMacros:bc,_fnInitialise:Aa,_fnInitComplete:Ra,_fnLengthChange:pb,_fnFeatureHtmlLength:Nb,_fnFeatureHtmlPaginate:Sb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:Pb,_fnProcessingDisplay:V,_fnFeatureHtmlTable:Qb,_fnScrollDraw:Ja,_fnApplyToChildren:da,_fnCalculateColumnWidths:db,_fnThrottle:mb,_fnConvertToWidth:cc,_fnGetWidestNode:dc,_fnGetMaxLenString:ec,_fnStringToCss:K,_fnSortFlatten:oa,_fnSort:Lb,
_fnSortAria:gc,_fnSortListener:rb,_fnSortAttachListener:kb,_fnSortingClasses:Va,_fnSortData:fc,_fnSaveState:Da,_fnLoadState:hc,_fnImplementState:tb,_fnSettingsFromNode:Wa,_fnLog:ea,_fnMap:Y,_fnBindAction:sb,_fnCallbackReg:R,_fnCallbackFire:F,_fnLengthOverflow:qb,_fnRenderer:lb,_fnDataSource:Q,_fnRowAttributes:jb,_fnExtend:ub,_fnCalculateEnd:function(){}});l.fn.dataTable=u;u.$=l;l.fn.dataTableSettings=u.settings;l.fn.dataTableExt=u.ext;l.fn.DataTable=function(a){return l(this).dataTable(a).api()};
l.each(u,function(a,b){l.fn.DataTable[a]=b});return u});
/*!
DataTables styling integration
©2018 SpryMedia Ltd - datatables.net/license
*/
(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);b&&b.fn.dataTable||(b=require("datatables.net")(a,b).$);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c,a,b,d){return c.fn.dataTable});
@@ -0,0 +1,41 @@
( function ( $ ) {
'use strict';
$( document ).on( 'click', '.asenha-user-account-toggle', function ( e ) {
e.preventDefault();
var $link = $( this );
var userId = parseInt( $link.data( 'user-id' ), 10 );
var setDisabled = parseInt( $link.attr( 'data-set-disabled' ), 10 ) === 1;
if ( ! userId || typeof asenhaDisableUserAccount === 'undefined' ) {
return;
}
$link.css( 'pointer-events', 'none' );
$.post( asenhaDisableUserAccount.ajaxUrl, {
action: 'asenha_user_account_toggle_disabled',
nonce: asenhaDisableUserAccount.nonce,
user_id: userId,
set_disabled: setDisabled ? 1 : 0
} )
.done( function ( response ) {
if ( ! response || ! response.success || ! response.data ) {
return;
}
var $row = $( '#user-' + userId );
$row.find( '.column-asenha_user_account_status' ).html(
response.data.status_html
);
$link.text( response.data.action_label );
$link.attr(
'data-set-disabled',
response.data.next_set_disabled
);
} )
.always( function () {
$link.css( 'pointer-events', '' );
} );
} );
} )( jQuery );

Some files were not shown because too many files have changed in this diff Show More