From ac2e4eb89d102fb09d6af36642c55b0a1b469463 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Mon, 31 Aug 2015 04:07:11 -0500 Subject: [PATCH 001/557] Don't include the vendor directory. --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ From 83f3430c233fffe6773d7b6f55356bd90503948a Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Mon, 31 Aug 2015 04:07:25 -0500 Subject: [PATCH 002/557] Initial plugin commit. --- composer.json | 16 ++++ composer.lock | 189 +++++++++++++++++++++++++++++++++++++++++++ index.php | 61 ++++++++++++++ src/js/run.js | 19 +++++ src/wpephpcompat.php | 38 +++++++++ 5 files changed, 323 insertions(+) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 index.php create mode 100644 src/js/run.js create mode 100644 src/wpephpcompat.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f1b81ca --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "name": "wpengine/phpcompat", + "description": "Test PHP Compatibility.", + "authors": [{ + "name": "Jason Stallings", + "email": "jason@stallin.gs" + }], + "require": { + "squizlabs/php_codesniffer": "*", + "wimg/php-compatibility": "dev-master", + "simplyadmire/composer-plugins" : "@dev" + }, + "autoload": { + "classmap": ["src/wpephpcompat.php"] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..400fb4f --- /dev/null +++ b/composer.lock @@ -0,0 +1,189 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "fb71b2362767b91e8537e5aeed50c6bf", + "packages": [ + { + "name": "simplyadmire/composer-plugins", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/SimplyAdmire/ComposerPlugins.git", + "reference": "9e0c76952a71caa1d953ee5779b7d4c4c298d142" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SimplyAdmire/ComposerPlugins/zipball/9e0c76952a71caa1d953ee5779b7d4c4c298d142", + "reference": "9e0c76952a71caa1d953ee5779b7d4c4c298d142", + "shasum": "" + }, + "require": { + "composer-plugin-api": "1.0.0", + "squizlabs/php_codesniffer": "*" + }, + "type": "composer-plugin", + "extra": { + "class": [ + "SimplyAdmire\\ComposerPlugins\\PhpCodesnifferStandardInstallerPlugin" + ] + }, + "autoload": { + "psr-0": { + "SimplyAdmire\\ComposerPlugins": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0+" + ], + "authors": [ + { + "name": "Rens Admiraal", + "email": "rens@simplyadmire.com", + "role": "lead" + } + ], + "description": "Composer plugin for installing PHP_CodeSniffer standards", + "keywords": [ + "PHP_CodeSniffer", + "TYPO3 CMS", + "TYPO3 Flow", + "TYPO3 Neos", + "phpcs", + "standards", + "typo3" + ], + "time": "2015-06-21 20:23:06" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "5046b0e01c416fc2b06df961d0673c85bcdc896c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5046b0e01c416fc2b06df961d0673c85bcdc896c", + "reference": "5046b0e01c416fc2b06df961d0673c85bcdc896c", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2015-03-04 02:07:03" + }, + { + "name": "wimg/php-compatibility", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/wimg/PHPCompatibility.git", + "reference": "e8982ebe6e6e14a0c1d2181b49ac234884d2d86d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wimg/PHPCompatibility/zipball/e8982ebe6e6e14a0c1d2181b49ac234884d2d86d", + "reference": "e8982ebe6e6e14a0c1d2181b49ac234884d2d86d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.1.2", + "squizlabs/php_codesniffer": ">=1.5.1,<=2.3" + }, + "require-dev": { + "satooshi/php-coveralls": "dev-master" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + } + ], + "description": "This is a set of sniffs for PHP_CodeSniffer that checks for PHP version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2015-08-25 14:36:52" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "wimg/php-compatibility": 20, + "simplyadmire/composer-plugins": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..f07b81b --- /dev/null +++ b/index.php @@ -0,0 +1,61 @@ +testVersion = "5.5"; + $report = $wpephpc->runTest(); + + echo $report; + + wp_die(); +} + +function wpephpcompat_enqueue() +{ + wp_enqueue_script( 'ajax-script', plugins_url( '/src/js/run.js', __FILE__ ), array('jquery') ); + + wp_localize_script( 'ajax-script', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' )) ); +} + +function wpephpcompat_create_menu() +{ + //Create Tools sub-menu. + $wpeallowheartbeat_settings_page = add_submenu_page('tools.php', 'PHP Compatibility', 'PHP Compatibility', 'administrator', __FILE__, 'wpephpcompat_settings_page'); +} + +function wpephpcompat_settings_page() +{ + + ?> +
+

WP Engine PHP Compatibility

+

+ Test Results: + +

+

+

+ +
+cli = new PHP_CodeSniffer_CLI(); + } + + public function runTest() + { + //FIXME: Need to replace WP_CONTENT_DIR, and exclude this plugin from the search. + $this->values['files'] = array(WP_CONTENT_DIR); + $this->values['testVersion'] = $this->testVersion; + $this->values['standard'] = "PHPCompatibility"; + $this->values['reportWidth'] = "9999"; + + ob_start(); + + $this->cli->process($this->values); + + $report = ob_get_contents(); + + ob_end_clean(); + + return $report; + } +} + +?> From 3125749354265a75bbc4ae46af77d89b99d73b1d Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Mon, 31 Aug 2015 18:13:00 -0500 Subject: [PATCH 003/557] Unique handle for our JavaScript. --- index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index f07b81b..213e5f3 100644 --- a/index.php +++ b/index.php @@ -32,9 +32,9 @@ function wpephpcompat_run_test() function wpephpcompat_enqueue() { - wp_enqueue_script( 'ajax-script', plugins_url( '/src/js/run.js', __FILE__ ), array('jquery') ); + wp_enqueue_script( 'wpephpcompat', plugins_url( '/src/js/run.js', __FILE__ ), array('jquery') ); - wp_localize_script( 'ajax-script', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' )) ); + wp_localize_script( 'wpephpcompat', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' )) ); } function wpephpcompat_create_menu() From f27e536d99ba9c60c9beb6c1d3a10736fbac024a Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:36:24 -0500 Subject: [PATCH 004/557] Disable textarea but keep the normal style. --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index 213e5f3..1b046dd 100644 --- a/index.php +++ b/index.php @@ -51,7 +51,7 @@ function wpephpcompat_settings_page()

WP Engine PHP Compatibility

Test Results: - +

From 19ff0fcfd00877918d24b9e99c67360487a9f1de Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:37:03 -0500 Subject: [PATCH 005/557] Changed the run button style. --- index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.php b/index.php index 1b046dd..444ce08 100644 --- a/index.php +++ b/index.php @@ -53,7 +53,7 @@ function wpephpcompat_settings_page() Test Results:

-

+

From 049d2af4ea1ee0842d9b876f3888144918fcf73e Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:37:36 -0500 Subject: [PATCH 006/557] Unselect run button so it's not highlighted. --- src/js/run.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/run.js b/src/js/run.js index 862e5ef..3696962 100644 --- a/src/js/run.js +++ b/src/js/run.js @@ -2,6 +2,8 @@ jQuery(document).ready(function($) { $("#runButton").on("click", function() { + //Unselect button so it's not highlighted. + $("#runButton").blur(); $(".spinner").show(); $("#testResults").text(""); var data = From a5ea89e3385ea00791f7c52c02a46ceee96667d5 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:38:09 -0500 Subject: [PATCH 007/557] Don't run scan if it's already running. This is temporary. --- src/js/run.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/js/run.js b/src/js/run.js index 3696962..f35b169 100644 --- a/src/js/run.js +++ b/src/js/run.js @@ -4,6 +4,13 @@ jQuery(document).ready(function($) { //Unselect button so it's not highlighted. $("#runButton").blur(); + + //If run button is disabled, don't run test. + if ($("#runButton").hasClass("button-primary-disabled")) + { + alert("Scan is already running!"); + return; + } $(".spinner").show(); $("#testResults").text(""); var data = From b4cf0101dfb80ca65616c92013d4429c8f79afc1 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:38:30 -0500 Subject: [PATCH 008/557] Disable the run button while scan is running. --- src/js/run.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/run.js b/src/js/run.js index f35b169..8064cc5 100644 --- a/src/js/run.js +++ b/src/js/run.js @@ -11,6 +11,9 @@ jQuery(document).ready(function($) alert("Scan is already running!"); return; } + + //Disable run button. + $("#runButton").addClass("button-primary-disabled"); $(".spinner").show(); $("#testResults").text(""); var data = From d93f0aab819a27062920a38efd30051a581beb43 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:38:45 -0500 Subject: [PATCH 009/557] Enable the run button when scan finishes. --- src/js/run.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/run.js b/src/js/run.js index 8064cc5..9f29af5 100644 --- a/src/js/run.js +++ b/src/js/run.js @@ -23,6 +23,7 @@ jQuery(document).ready(function($) jQuery.post(ajax_object.ajax_url, data, function(response) { + $("#runButton").removeClass("button-primary-disabled"); $(".spinner").hide(); $("#testResults").text(response); }); From a456911adefccf52bb4d6a9bb6fab7b32f2bb590 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:38:55 -0500 Subject: [PATCH 010/557] Added some comments. --- src/js/run.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/run.js b/src/js/run.js index 9f29af5..f970a99 100644 --- a/src/js/run.js +++ b/src/js/run.js @@ -14,8 +14,11 @@ jQuery(document).ready(function($) //Disable run button. $("#runButton").addClass("button-primary-disabled"); + //Show the ajax spinner. $(".spinner").show(); + //Empty the results textarea. $("#testResults").text(""); + var data = { 'action': 'wpephpcompat_run_test' From 13f365e18dfa5ed8545d1967552e106c761cc664 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:41:10 -0500 Subject: [PATCH 011/557] Added some comments to the WPEPHPCompat class. --- src/wpephpcompat.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 0fd28ec..b9235df 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -4,10 +4,22 @@ class WPEPHPCompat { + /** + * The PHP_CodeSniffer_CLI object. + * @var class + */ public $cli = null; + /** + * Default values for PHP_CodeSniffer scan. + * @var array + */ public $values = array(); + /** + * Version of PHP to test. + * @var string + */ public $testVersion = null; function __construct() @@ -15,6 +27,10 @@ function __construct() $this->cli = new PHP_CodeSniffer_CLI(); } + /** + * Runs the actual PHPCompatibility test. + * @return string Scan results. + */ public function runTest() { //FIXME: Need to replace WP_CONTENT_DIR, and exclude this plugin from the search. From 0ba11b6b4c0469af75f9f103a22a9d7a3c9fb626 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:41:33 -0500 Subject: [PATCH 012/557] Added generateFileList(). --- src/wpephpcompat.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index b9235df..d925c87 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -47,6 +47,16 @@ public function runTest() ob_end_clean(); + /** + * Generate a list of files to scan. + * @return array Array of files to scan. + */ + private function generateFileList() + { + //FIXME: Need to replace WP_CONTENT_DIR with a list of directories to scan. + return array(WP_CONTENT_DIR); + } + return $report; } } From 27af7e063d22ee5fdc5b4c5fc4ce4a0f32676732 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:41:54 -0500 Subject: [PATCH 013/557] Added generateIgnoreList(). --- src/wpephpcompat.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index d925c87..6e5ed39 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -57,6 +57,17 @@ private function generateFileList() return array(WP_CONTENT_DIR); } + /** + * Generate a list of files to ignore. + * @return array Array of files to exclude from the scan. + */ + private function generateIgnoreList() + { + //Get this plugins relative directory. + $pluginDir = dirname(plugin_basename(__DIR__)); + return array($pluginDir); + } + return $report; } } From 1b7c31dc20c0f51a58c45e518cbb1fc44e2d95fb Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:42:05 -0500 Subject: [PATCH 014/557] Added cleanReport(). --- src/wpephpcompat.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 6e5ed39..a572134 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -68,6 +68,19 @@ private function generateIgnoreList() return array($pluginDir); } + /** + * Cleans and formats the final report. + * @param string $report The full report. + * @return string The cleaned report. + */ + private function cleanReport($report) + { + //Remove unnecessary overview. + $report = preg_replace ('/Time:.+\n/si', '', $report); + + //Remove whitespace. + $report = trim($report); + return $report; } } From 9da856163264551b94e595bffd2841c98c9b6b6d Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:42:42 -0500 Subject: [PATCH 015/557] Use new generateFIleList and generateIgnoreList functions. --- src/wpephpcompat.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index a572134..35410df 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -33,8 +33,8 @@ function __construct() */ public function runTest() { - //FIXME: Need to replace WP_CONTENT_DIR, and exclude this plugin from the search. - $this->values['files'] = array(WP_CONTENT_DIR); + $this->values['files'] = $this->generateFileList(); + $this->values['ignored'] = $this->generateIgnoreList(); $this->values['testVersion'] = $this->testVersion; $this->values['standard'] = "PHPCompatibility"; $this->values['reportWidth'] = "9999"; From 04bce90774c3c4fbfdc075af2035713521bffdd0 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:42:55 -0500 Subject: [PATCH 016/557] Clean the final report with cleanReport(). --- src/wpephpcompat.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 35410df..6889867 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -47,6 +47,9 @@ public function runTest() ob_end_clean(); + return $this->cleanReport($report); + } + /** * Generate a list of files to scan. * @return array Array of files to scan. From 97b1088e15a174a49d0ff5f15d3b1dcf180b101e Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sun, 6 Sep 2015 07:53:11 -0500 Subject: [PATCH 017/557] Change function name to be more accurate. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function won’t run the test, just get it started. --- index.php | 4 ++-- src/js/run.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.php b/index.php index 444ce08..4c23086 100644 --- a/index.php +++ b/index.php @@ -15,9 +15,9 @@ //Load our JavaScript. add_action( 'admin_enqueue_scripts', 'wpephpcompat_enqueue' ); //The action to run the compatibility test. -add_action('wp_ajax_wpephpcompat_run_test', 'wpephpcompat_run_test'); +add_action('wp_ajax_wpephpcompat_run_test', 'wpephpcompat_start_test'); -function wpephpcompat_run_test() +function wpephpcompat_start_test() { //TODO: Allow setting testVersion from the UI. diff --git a/src/js/run.js b/src/js/run.js index f970a99..0f2ff7d 100644 --- a/src/js/run.js +++ b/src/js/run.js @@ -21,7 +21,7 @@ jQuery(document).ready(function($) var data = { - 'action': 'wpephpcompat_run_test' + 'action': 'wpephpcompat_start_test' }; jQuery.post(ajax_object.ajax_url, data, function(response) From a31e522b1d016c96fd5273aedab9d6e871beb0af Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Thu, 10 Sep 2015 09:55:48 -0500 Subject: [PATCH 018/557] Cron running implemented. --- index.php | 104 +++++++++++++++++++++++++++++++++++++++---- src/wpephpcompat.php | 6 +-- 2 files changed, 99 insertions(+), 11 deletions(-) diff --git a/index.php b/index.php index 4c23086..87d9015 100644 --- a/index.php +++ b/index.php @@ -15,19 +15,104 @@ //Load our JavaScript. add_action( 'admin_enqueue_scripts', 'wpephpcompat_enqueue' ); //The action to run the compatibility test. -add_action('wp_ajax_wpephpcompat_run_test', 'wpephpcompat_start_test'); +add_action('wp_ajax_wpephpcompat_start_test', 'wpephpcompat_start_test'); +add_action('wpephpcompat_start_test_cron', 'wpephpcompat_start_test'); +//Create custom post type. +add_action( 'init', 'wpephpcompat_create_job_queue' ); function wpephpcompat_start_test() { + global $wpdb; + error_log("started"); + $lock_name = 'wpephpcompat.lock'; + $scan_status_name = 'wpephpcompat.status'; + + // Try to lock. + $lock_result = add_option($lock_name, time(), '', 'no' ); + + error_log("lock: ". $lock_result); + + if (!$lock_result) + { + $lock_result = get_option($lock_name); - //TODO: Allow setting testVersion from the UI. - $wpephpc = new \WPEPHPCompat(); - $wpephpc->testVersion = "5.5"; - $report = $wpephpc->runTest(); - - echo $report; + // Bail if we were unable to create a lock, or if the existing lock is still valid. + if ( ! $lock_result || ( $lock_result > ( time() - MINUTE_IN_SECONDS ) ) ) + { + error_log("Locked, this would have returned."); + return; + } + } + update_option($lock_name, time()); + + //Check to see if scan has already started. + $scan_status = get_option($scan_status_name); + error_log("scan status: " . $scan_status); + if (!$scan_status) + { + $dir = array( + 'post_title' => "test", + 'post_content' => "/vagrant/content/plugins/test", + 'post_status' => 'publish', + 'post_author' => 1, + 'post_type' => 'wpephpcompat_jobs' + ); + error_log("Insert post:" . wp_insert_post( $dir )); + + update_option($scan_status_name, "1"); + } + + $args = array('posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs'); + $directories = get_posts($args); + error_log("After getting posts."); + + //If there are no directories to scan, we're finished! + if (!$directories) + { + error_log("no posts"); + delete_option($lock_name); + delete_option($scan_status_name); + return; + } + + wp_schedule_single_event( time() + ( MINUTE_IN_SECONDS ), 'wpephpcompat_start_test_cron' ); + + $scan_results = get_option("wpephpcompat_scan_results"); + + foreach ($directories as $directory) + { + $wpephpc = new \WPEPHPCompat(); + $wpephpc->testVersion = "5.5"; + $report = $wpephpc->runTest($directory->post_content); + $scan_results .= $report . "\n"; + update_option("wpephpcompat_scan_results", $scan_results); + wp_delete_post($directory->ID); + } + + echo $scan_results; + + //All scans finished, clean up! + delete_option($scan_status_name); + delete_option("wpephpcompat_scan_results"); + delete_option("wpephpcompat_scan_results"); + wp_clear_scheduled_hook("wpephpcompat_start_test_cron"); - wp_die(); + delete_option($lock_name); + wp_die(); +} + +function wpephpcompat_create_job_queue() +{ + register_post_type( 'wpephpcompat_jobs', + array( + 'labels' => array( + 'name' => __( 'Jobs' ), + 'singular_name' => __( 'Job' ) + ), + 'public' => false, + 'has_archive' => false, + ) + ); } function wpephpcompat_enqueue() @@ -50,6 +135,9 @@ function wpephpcompat_settings_page()

WP Engine PHP Compatibility

+ Test Results:

diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 6889867..481eee6 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -31,10 +31,10 @@ function __construct() * Runs the actual PHPCompatibility test. * @return string Scan results. */ - public function runTest() + public function runTest($dir) { - $this->values['files'] = $this->generateFileList(); - $this->values['ignored'] = $this->generateIgnoreList(); + $this->values['files'] = $dir; + //$this->values['ignored'] = $this->generateIgnoreList(); $this->values['testVersion'] = $this->testVersion; $this->values['standard'] = "PHPCompatibility"; $this->values['reportWidth'] = "9999"; From 50ce93c8d016e24d283bdc121e43f30a72e1089b Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sat, 12 Sep 2015 07:57:53 -0500 Subject: [PATCH 019/557] Need to queue the actual plugins. --- index.php | 1 + 1 file changed, 1 insertion(+) diff --git a/index.php b/index.php index 87d9015..22175d9 100644 --- a/index.php +++ b/index.php @@ -50,6 +50,7 @@ function wpephpcompat_start_test() error_log("scan status: " . $scan_status); if (!$scan_status) { + //FIXME: Queue actual plugins. $dir = array( 'post_title' => "test", 'post_content' => "/vagrant/content/plugins/test", From 52f20c5efa0ff95a718c9d907292221646fe2f12 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Tue, 6 Oct 2015 04:44:17 -0500 Subject: [PATCH 020/557] Include handlebars. --- src/js/handlebars.js | 4607 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4607 insertions(+) create mode 100644 src/js/handlebars.js diff --git a/src/js/handlebars.js b/src/js/handlebars.js new file mode 100644 index 0000000..37645bb --- /dev/null +++ b/src/js/handlebars.js @@ -0,0 +1,4607 @@ +/*! + + handlebars v4.0.3 + +Copyright (C) 2011-2015 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["Handlebars"] = factory(); + else + root["Handlebars"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _handlebarsRuntime = __webpack_require__(2); + + var _handlebarsRuntime2 = _interopRequireDefault(_handlebarsRuntime); + + // Compiler imports + + var _handlebarsCompilerAst = __webpack_require__(21); + + var _handlebarsCompilerAst2 = _interopRequireDefault(_handlebarsCompilerAst); + + var _handlebarsCompilerBase = __webpack_require__(22); + + var _handlebarsCompilerCompiler = __webpack_require__(27); + + var _handlebarsCompilerJavascriptCompiler = __webpack_require__(28); + + var _handlebarsCompilerJavascriptCompiler2 = _interopRequireDefault(_handlebarsCompilerJavascriptCompiler); + + var _handlebarsCompilerVisitor = __webpack_require__(25); + + var _handlebarsCompilerVisitor2 = _interopRequireDefault(_handlebarsCompilerVisitor); + + var _handlebarsNoConflict = __webpack_require__(20); + + var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); + + var _create = _handlebarsRuntime2['default'].create; + function create() { + var hb = _create(); + + hb.compile = function (input, options) { + return _handlebarsCompilerCompiler.compile(input, options, hb); + }; + hb.precompile = function (input, options) { + return _handlebarsCompilerCompiler.precompile(input, options, hb); + }; + + hb.AST = _handlebarsCompilerAst2['default']; + hb.Compiler = _handlebarsCompilerCompiler.Compiler; + hb.JavaScriptCompiler = _handlebarsCompilerJavascriptCompiler2['default']; + hb.Parser = _handlebarsCompilerBase.parser; + hb.parse = _handlebarsCompilerBase.parse; + + return hb; + } + + var inst = create(); + inst.create = create; + + _handlebarsNoConflict2['default'](inst); + + inst.Visitor = _handlebarsCompilerVisitor2['default']; + + inst['default'] = inst; + + exports['default'] = inst; + module.exports = exports['default']; + +/***/ }, +/* 1 */ +/***/ function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + return obj && obj.__esModule ? obj : { + "default": obj + }; + }; + + exports.__esModule = true; + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _handlebarsBase = __webpack_require__(4); + + var base = _interopRequireWildcard(_handlebarsBase); + + // Each of these augment the Handlebars object. No need to setup here. + // (This is done to easily share code between commonjs and browse envs) + + var _handlebarsSafeString = __webpack_require__(18); + + var _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString); + + var _handlebarsException = __webpack_require__(6); + + var _handlebarsException2 = _interopRequireDefault(_handlebarsException); + + var _handlebarsUtils = __webpack_require__(5); + + var Utils = _interopRequireWildcard(_handlebarsUtils); + + var _handlebarsRuntime = __webpack_require__(19); + + var runtime = _interopRequireWildcard(_handlebarsRuntime); + + var _handlebarsNoConflict = __webpack_require__(20); + + var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); + + // For compatibility and usage outside of module systems, make the Handlebars object a namespace + function create() { + var hb = new base.HandlebarsEnvironment(); + + Utils.extend(hb, base); + hb.SafeString = _handlebarsSafeString2['default']; + hb.Exception = _handlebarsException2['default']; + hb.Utils = Utils; + hb.escapeExpression = Utils.escapeExpression; + + hb.VM = runtime; + hb.template = function (spec) { + return runtime.template(spec, hb); + }; + + return hb; + } + + var inst = create(); + inst.create = create; + + _handlebarsNoConflict2['default'](inst); + + inst['default'] = inst; + + exports['default'] = inst; + module.exports = exports['default']; + +/***/ }, +/* 3 */ +/***/ function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; + } + } + + newObj["default"] = obj; + return newObj; + } + }; + + exports.__esModule = true; + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.HandlebarsEnvironment = HandlebarsEnvironment; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _helpers = __webpack_require__(7); + + var _decorators = __webpack_require__(15); + + var _logger = __webpack_require__(17); + + var _logger2 = _interopRequireDefault(_logger); + + var VERSION = '4.0.3'; + exports.VERSION = VERSION; + var COMPILER_REVISION = 7; + + exports.COMPILER_REVISION = COMPILER_REVISION; + var REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '== 1.x.x', + 5: '== 2.0.0-alpha.x', + 6: '>= 2.0.0-beta.1', + 7: '>= 4.0.0' + }; + + exports.REVISION_CHANGES = REVISION_CHANGES; + var objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials, decorators) { + this.helpers = helpers || {}; + this.partials = partials || {}; + this.decorators = decorators || {}; + + _helpers.registerDefaultHelpers(this); + _decorators.registerDefaultDecorators(this); + } + + HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: _logger2['default'], + log: _logger2['default'].log, + + registerHelper: function registerHelper(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple helpers'); + } + _utils.extend(this.helpers, name); + } else { + this.helpers[name] = fn; + } + }, + unregisterHelper: function unregisterHelper(name) { + delete this.helpers[name]; + }, + + registerPartial: function registerPartial(name, partial) { + if (_utils.toString.call(name) === objectType) { + _utils.extend(this.partials, name); + } else { + if (typeof partial === 'undefined') { + throw new _exception2['default']('Attempting to register a partial as undefined'); + } + this.partials[name] = partial; + } + }, + unregisterPartial: function unregisterPartial(name) { + delete this.partials[name]; + }, + + registerDecorator: function registerDecorator(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple decorators'); + } + _utils.extend(this.decorators, name); + } else { + this.decorators[name] = fn; + } + }, + unregisterDecorator: function unregisterDecorator(name) { + delete this.decorators[name]; + } + }; + + var log = _logger2['default'].log; + + exports.log = log; + exports.createFrame = _utils.createFrame; + exports.logger = _logger2['default']; + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.extend = extend; + exports.indexOf = indexOf; + exports.escapeExpression = escapeExpression; + exports.isEmpty = isEmpty; + exports.createFrame = createFrame; + exports.blockParams = blockParams; + exports.appendContextPath = appendContextPath; + var escape = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`', + '=': '=' + }; + + var badChars = /[&<>"'`=]/g, + possible = /[&<>"'`=]/; + + function escapeChar(chr) { + return escape[chr]; + } + + function extend(obj /* , ...source */) { + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } + } + } + + return obj; + } + + var toString = Object.prototype.toString; + + exports.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + /* eslint-disable func-style */ + var isFunction = function isFunction(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + /* istanbul ignore next */ + if (isFunction(/x/)) { + exports.isFunction = isFunction = function (value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + exports.isFunction = isFunction; + + /* eslint-enable func-style */ + + /* istanbul ignore next */ + var isArray = Array.isArray || function (value) { + return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; + }; + + exports.isArray = isArray; + // Older IE versions do not directly support indexOf so we must implement our own, sadly. + + function indexOf(array, value) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + return -1; + } + + function escapeExpression(string) { + if (typeof string !== 'string') { + // don't escape SafeStrings, since they're already safe + if (string && string.toHTML) { + return string.toHTML(); + } else if (string == null) { + return ''; + } else if (!string) { + return string + ''; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = '' + string; + } + + if (!possible.test(string)) { + return string; + } + return string.replace(badChars, escapeChar); + } + + function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + function createFrame(object) { + var frame = extend({}, object); + frame._parent = object; + return frame; + } + + function blockParams(params, ids) { + params.path = ids; + return params; + } + + function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; + } + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var loc = node && node.loc, + line = undefined, + column = undefined; + if (loc) { + line = loc.start.line; + column = loc.start.column; + + message += ' - ' + line + ':' + column; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + /* istanbul ignore else */ + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Exception); + } + + if (loc) { + this.lineNumber = line; + this.column = column; + } + } + + Exception.prototype = new Error(); + + exports['default'] = Exception; + module.exports = exports['default']; + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultHelpers = registerDefaultHelpers; + + var _helpersBlockHelperMissing = __webpack_require__(8); + + var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing); + + var _helpersEach = __webpack_require__(9); + + var _helpersEach2 = _interopRequireDefault(_helpersEach); + + var _helpersHelperMissing = __webpack_require__(10); + + var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing); + + var _helpersIf = __webpack_require__(11); + + var _helpersIf2 = _interopRequireDefault(_helpersIf); + + var _helpersLog = __webpack_require__(12); + + var _helpersLog2 = _interopRequireDefault(_helpersLog); + + var _helpersLookup = __webpack_require__(13); + + var _helpersLookup2 = _interopRequireDefault(_helpersLookup); + + var _helpersWith = __webpack_require__(14); + + var _helpersWith2 = _interopRequireDefault(_helpersWith); + + function registerDefaultHelpers(instance) { + _helpersBlockHelperMissing2['default'](instance); + _helpersEach2['default'](instance); + _helpersHelperMissing2['default'](instance); + _helpersIf2['default'](instance); + _helpersLog2['default'](instance); + _helpersLookup2['default'](instance); + _helpersWith2['default'](instance); + } + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('blockHelperMissing', function (context, options) { + var inverse = options.inverse, + fn = options.fn; + + if (context === true) { + return fn(this); + } else if (context === false || context == null) { + return inverse(this); + } else if (_utils.isArray(context)) { + if (context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + if (options.data && options.ids) { + var data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name); + options = { data: data }; + } + + return fn(context, options); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('each', function (context, options) { + if (!options) { + throw new _exception2['default']('Must pass iterator to #each'); + } + + var fn = options.fn, + inverse = options.inverse, + i = 0, + ret = '', + data = undefined, + contextPath = undefined; + + if (options.data && options.ids) { + contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + + if (_utils.isFunction(context)) { + context = context.call(this); + } + + if (options.data) { + data = _utils.createFrame(options.data); + } + + function execIteration(field, index, last) { + if (data) { + data.key = field; + data.index = index; + data.first = index === 0; + data.last = !!last; + + if (contextPath) { + data.contextPath = contextPath + field; + } + } + + ret = ret + fn(context[field], { + data: data, + blockParams: _utils.blockParams([context[field], field], [contextPath + field, null]) + }); + } + + if (context && typeof context === 'object') { + if (_utils.isArray(context)) { + for (var j = context.length; i < j; i++) { + if (i in context) { + execIteration(i, i, i === context.length - 1); + } + } + } else { + var priorKey = undefined; + + for (var key in context) { + if (context.hasOwnProperty(key)) { + // We're running the iterations one step out of sync so we can detect + // the last iteration without have to scan the object twice and create + // an itermediate keys array. + if (priorKey !== undefined) { + execIteration(priorKey, i - 1); + } + priorKey = key; + i++; + } + } + if (priorKey !== undefined) { + execIteration(priorKey, i - 1, true); + } + } + } + + if (i === 0) { + ret = inverse(this); + } + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('helperMissing', function () /* [args, ]options */{ + if (arguments.length === 1) { + // A missing field in a {{foo}} construct. + return undefined; + } else { + // Someone is actually trying to call something, blow up. + throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"'); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('if', function (conditional, options) { + if (_utils.isFunction(conditional)) { + conditional = conditional.call(this); + } + + // Default behavior is to render the positive path if the value is truthy and not empty. + // The `includeZero` option may be set to treat the condtional as purely not empty based on the + // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. + if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) { + return options.inverse(this); + } else { + return options.fn(this); + } + }); + + instance.registerHelper('unless', function (conditional, options) { + return instance.helpers['if'].call(this, conditional, { fn: options.inverse, inverse: options.fn, hash: options.hash }); + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 12 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('log', function () /* message, options */{ + var args = [undefined], + options = arguments[arguments.length - 1]; + for (var i = 0; i < arguments.length - 1; i++) { + args.push(arguments[i]); + } + + var level = 1; + if (options.hash.level != null) { + level = options.hash.level; + } else if (options.data && options.data.level != null) { + level = options.data.level; + } + args[0] = level; + + instance.log.apply(instance, args); + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('lookup', function (obj, field) { + return obj && obj[field]; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('with', function (context, options) { + if (_utils.isFunction(context)) { + context = context.call(this); + } + + var fn = options.fn; + + if (!_utils.isEmpty(context)) { + var data = options.data; + if (options.data && options.ids) { + data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]); + } + + return fn(context, { + data: data, + blockParams: _utils.blockParams([context], [data && data.contextPath]) + }); + } else { + return options.inverse(this); + } + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 15 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultDecorators = registerDefaultDecorators; + + var _decoratorsInline = __webpack_require__(16); + + var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline); + + function registerDefaultDecorators(instance) { + _decoratorsInline2['default'](instance); + } + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerDecorator('inline', function (fn, props, container, options) { + var ret = fn; + if (!props.partials) { + props.partials = {}; + ret = function (context, options) { + // Create a new partials stack frame prior to exec. + var original = container.partials; + container.partials = _utils.extend({}, original, props.partials); + var ret = fn(context, options); + container.partials = original; + return ret; + }; + } + + props.partials[options.args[0]] = options.fn; + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }, +/* 17 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var logger = { + methodMap: ['debug', 'info', 'warn', 'error'], + level: 'info', + + // Maps a given level value to the `methodMap` indexes above. + lookupLevel: function lookupLevel(level) { + if (typeof level === 'string') { + var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase()); + if (levelMap >= 0) { + level = levelMap; + } else { + level = parseInt(level, 10); + } + } + + return level; + }, + + // Can be overridden in the host environment + log: function log(level) { + level = logger.lookupLevel(level); + + if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { + var method = logger.methodMap[level]; + if (!console[method]) { + // eslint-disable-line no-console + method = 'log'; + } + + for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + message[_key - 1] = arguments[_key]; + } + + console[method].apply(console, message); // eslint-disable-line no-console + } + } + }; + + exports['default'] = logger; + module.exports = exports['default']; + +/***/ }, +/* 18 */ +/***/ function(module, exports) { + + // Build out our basic SafeString type + 'use strict'; + + exports.__esModule = true; + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = SafeString.prototype.toHTML = function () { + return '' + this.string; + }; + + exports['default'] = SafeString; + module.exports = exports['default']; + +/***/ }, +/* 19 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.checkRevision = checkRevision; + exports.template = template; + exports.wrapProgram = wrapProgram; + exports.resolvePartial = resolvePartial; + exports.invokePartial = invokePartial; + exports.noop = noop; + + var _utils = __webpack_require__(5); + + var Utils = _interopRequireWildcard(_utils); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _base = __webpack_require__(4); + + function checkRevision(compilerInfo) { + var compilerRevision = compilerInfo && compilerInfo[0] || 1, + currentRevision = _base.COMPILER_REVISION; + + if (compilerRevision !== currentRevision) { + if (compilerRevision < currentRevision) { + var runtimeVersions = _base.REVISION_CHANGES[currentRevision], + compilerVersions = _base.REVISION_CHANGES[compilerRevision]; + throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); + } + } + } + + function template(templateSpec, env) { + /* istanbul ignore next */ + if (!env) { + throw new _exception2['default']('No environment passed to template'); + } + if (!templateSpec || !templateSpec.main) { + throw new _exception2['default']('Unknown template object: ' + typeof templateSpec); + } + + templateSpec.main.decorator = templateSpec.main_d; + + // Note: Using env.VM references rather than local var references throughout this section to allow + // for external users to override these as psuedo-supported APIs. + env.VM.checkRevision(templateSpec.compiler); + + function invokePartialWrapper(partial, context, options) { + if (options.hash) { + context = Utils.extend({}, context, options.hash); + if (options.ids) { + options.ids[0] = true; + } + } + + partial = env.VM.resolvePartial.call(this, partial, context, options); + var result = env.VM.invokePartial.call(this, partial, context, options); + + if (result == null && env.compile) { + options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); + result = options.partials[options.name](context, options); + } + if (result != null) { + if (options.indent) { + var lines = result.split('\n'); + for (var i = 0, l = lines.length; i < l; i++) { + if (!lines[i] && i + 1 === l) { + break; + } + + lines[i] = options.indent + lines[i]; + } + result = lines.join('\n'); + } + return result; + } else { + throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); + } + } + + // Just add water + var container = { + strict: function strict(obj, name) { + if (!(name in obj)) { + throw new _exception2['default']('"' + name + '" not defined in ' + obj); + } + return obj[name]; + }, + lookup: function lookup(depths, name) { + var len = depths.length; + for (var i = 0; i < len; i++) { + if (depths[i] && depths[i][name] != null) { + return depths[i][name]; + } + } + }, + lambda: function lambda(current, context) { + return typeof current === 'function' ? current.call(context) : current; + }, + + escapeExpression: Utils.escapeExpression, + invokePartial: invokePartialWrapper, + + fn: function fn(i) { + var ret = templateSpec[i]; + ret.decorator = templateSpec[i + '_d']; + return ret; + }, + + programs: [], + program: function program(i, data, declaredBlockParams, blockParams, depths) { + var programWrapper = this.programs[i], + fn = this.fn(i); + if (data || depths || blockParams || declaredBlockParams) { + programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); + } else if (!programWrapper) { + programWrapper = this.programs[i] = wrapProgram(this, i, fn); + } + return programWrapper; + }, + + data: function data(value, depth) { + while (value && depth--) { + value = value._parent; + } + return value; + }, + merge: function merge(param, common) { + var obj = param || common; + + if (param && common && param !== common) { + obj = Utils.extend({}, common, param); + } + + return obj; + }, + + noop: env.VM.noop, + compilerInfo: templateSpec.compiler + }; + + function ret(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var data = options.data; + + ret._setup(options); + if (!options.partial && templateSpec.useData) { + data = initData(context, data); + } + var depths = undefined, + blockParams = templateSpec.useBlockParams ? [] : undefined; + if (templateSpec.useDepths) { + if (options.depths) { + depths = context !== options.depths[0] ? [context].concat(options.depths) : options.depths; + } else { + depths = [context]; + } + } + + function main(context /*, options*/) { + return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); + } + main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); + return main(context, options); + } + ret.isTop = true; + + ret._setup = function (options) { + if (!options.partial) { + container.helpers = container.merge(options.helpers, env.helpers); + + if (templateSpec.usePartial) { + container.partials = container.merge(options.partials, env.partials); + } + if (templateSpec.usePartial || templateSpec.useDecorators) { + container.decorators = container.merge(options.decorators, env.decorators); + } + } else { + container.helpers = options.helpers; + container.partials = options.partials; + container.decorators = options.decorators; + } + }; + + ret._child = function (i, data, blockParams, depths) { + if (templateSpec.useBlockParams && !blockParams) { + throw new _exception2['default']('must pass block params'); + } + if (templateSpec.useDepths && !depths) { + throw new _exception2['default']('must pass parent depths'); + } + + return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); + }; + return ret; + } + + function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { + function prog(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var currentDepths = depths; + if (depths && context !== depths[0]) { + currentDepths = [context].concat(depths); + } + + return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths); + } + + prog = executeDecorators(fn, prog, container, depths, data, blockParams); + + prog.program = i; + prog.depth = depths ? depths.length : 0; + prog.blockParams = declaredBlockParams || 0; + return prog; + } + + function resolvePartial(partial, context, options) { + if (!partial) { + if (options.name === '@partial-block') { + partial = options.data['partial-block']; + } else { + partial = options.partials[options.name]; + } + } else if (!partial.call && !options.name) { + // This is a dynamic partial that returned a string + options.name = partial; + partial = options.partials[partial]; + } + return partial; + } + + function invokePartial(partial, context, options) { + options.partial = true; + if (options.ids) { + options.data.contextPath = options.ids[0] || options.data.contextPath; + } + + var partialBlock = undefined; + if (options.fn && options.fn !== noop) { + options.data = _base.createFrame(options.data); + partialBlock = options.data['partial-block'] = options.fn; + + if (partialBlock.partials) { + options.partials = Utils.extend({}, options.partials, partialBlock.partials); + } + } + + if (partial === undefined && partialBlock) { + partial = partialBlock; + } + + if (partial === undefined) { + throw new _exception2['default']('The partial ' + options.name + ' could not be found'); + } else if (partial instanceof Function) { + return partial(context, options); + } + } + + function noop() { + return ''; + } + + function initData(context, data) { + if (!data || !('root' in data)) { + data = data ? _base.createFrame(data) : {}; + data.root = context; + } + return data; + } + + function executeDecorators(fn, prog, container, depths, data, blockParams) { + if (fn.decorator) { + var props = {}; + prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); + Utils.extend(prog, props); + } + return prog; + } + +/***/ }, +/* 20 */ +/***/ function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/* global window */ + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (Handlebars) { + /* istanbul ignore next */ + var root = typeof global !== 'undefined' ? global : window, + $Handlebars = root.Handlebars; + /* istanbul ignore next */ + Handlebars.noConflict = function () { + if (root.Handlebars === Handlebars) { + root.Handlebars = $Handlebars; + } + }; + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }, +/* 21 */ +/***/ function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + var AST = { + // Public API used to evaluate derived attributes regarding AST nodes + helpers: { + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + helperExpression: function helperExpression(node) { + return node.type === 'SubExpression' || (node.type === 'MustacheStatement' || node.type === 'BlockStatement') && !!(node.params && node.params.length || node.hash); + }, + + scopedId: function scopedId(path) { + return (/^\.|this\b/.test(path.original) + ); + }, + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + simpleId: function simpleId(path) { + return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth; + } + } + }; + + // Must be exported as an object rather than the root of the module as the jison lexer + // must modify the object to operate properly. + exports['default'] = AST; + module.exports = exports['default']; + +/***/ }, +/* 22 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + exports.__esModule = true; + exports.parse = parse; + + var _parser = __webpack_require__(23); + + var _parser2 = _interopRequireDefault(_parser); + + var _whitespaceControl = __webpack_require__(24); + + var _whitespaceControl2 = _interopRequireDefault(_whitespaceControl); + + var _helpers = __webpack_require__(26); + + var Helpers = _interopRequireWildcard(_helpers); + + var _utils = __webpack_require__(5); + + exports.parser = _parser2['default']; + + var yy = {}; + _utils.extend(yy, Helpers); + + function parse(input, options) { + // Just return if an already-compiled AST was passed in. + if (input.type === 'Program') { + return input; + } + + _parser2['default'].yy = yy; + + // Altering the shared object here, but this is ok as parser is a sync operation + yy.locInfo = function (locInfo) { + return new yy.SourceLocation(options && options.srcName, locInfo); + }; + + var strip = new _whitespaceControl2['default'](options); + return strip.accept(_parser2['default'].parse(input)); + } + +/***/ }, +/* 23 */ +/***/ function(module, exports) { + + /* istanbul ignore next */ + /* Jison generated parser */ + "use strict"; + + var handlebars = (function () { + var parser = { trace: function trace() {}, + yy: {}, + symbols_: { "error": 2, "root": 3, "program": 4, "EOF": 5, "program_repetition0": 6, "statement": 7, "mustache": 8, "block": 9, "rawBlock": 10, "partial": 11, "partialBlock": 12, "content": 13, "COMMENT": 14, "CONTENT": 15, "openRawBlock": 16, "rawBlock_repetition_plus0": 17, "END_RAW_BLOCK": 18, "OPEN_RAW_BLOCK": 19, "helperName": 20, "openRawBlock_repetition0": 21, "openRawBlock_option0": 22, "CLOSE_RAW_BLOCK": 23, "openBlock": 24, "block_option0": 25, "closeBlock": 26, "openInverse": 27, "block_option1": 28, "OPEN_BLOCK": 29, "openBlock_repetition0": 30, "openBlock_option0": 31, "openBlock_option1": 32, "CLOSE": 33, "OPEN_INVERSE": 34, "openInverse_repetition0": 35, "openInverse_option0": 36, "openInverse_option1": 37, "openInverseChain": 38, "OPEN_INVERSE_CHAIN": 39, "openInverseChain_repetition0": 40, "openInverseChain_option0": 41, "openInverseChain_option1": 42, "inverseAndProgram": 43, "INVERSE": 44, "inverseChain": 45, "inverseChain_option0": 46, "OPEN_ENDBLOCK": 47, "OPEN": 48, "mustache_repetition0": 49, "mustache_option0": 50, "OPEN_UNESCAPED": 51, "mustache_repetition1": 52, "mustache_option1": 53, "CLOSE_UNESCAPED": 54, "OPEN_PARTIAL": 55, "partialName": 56, "partial_repetition0": 57, "partial_option0": 58, "openPartialBlock": 59, "OPEN_PARTIAL_BLOCK": 60, "openPartialBlock_repetition0": 61, "openPartialBlock_option0": 62, "param": 63, "sexpr": 64, "OPEN_SEXPR": 65, "sexpr_repetition0": 66, "sexpr_option0": 67, "CLOSE_SEXPR": 68, "hash": 69, "hash_repetition_plus0": 70, "hashSegment": 71, "ID": 72, "EQUALS": 73, "blockParams": 74, "OPEN_BLOCK_PARAMS": 75, "blockParams_repetition_plus0": 76, "CLOSE_BLOCK_PARAMS": 77, "path": 78, "dataName": 79, "STRING": 80, "NUMBER": 81, "BOOLEAN": 82, "UNDEFINED": 83, "NULL": 84, "DATA": 85, "pathSegments": 86, "SEP": 87, "$accept": 0, "$end": 1 }, + terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" }, + productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$ + /**/) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: + return $$[$0 - 1]; + break; + case 2: + this.$ = yy.prepareProgram($$[$0]); + break; + case 3: + this.$ = $$[$0]; + break; + case 4: + this.$ = $$[$0]; + break; + case 5: + this.$ = $$[$0]; + break; + case 6: + this.$ = $$[$0]; + break; + case 7: + this.$ = $$[$0]; + break; + case 8: + this.$ = $$[$0]; + break; + case 9: + this.$ = { + type: 'CommentStatement', + value: yy.stripComment($$[$0]), + strip: yy.stripFlags($$[$0], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 10: + this.$ = { + type: 'ContentStatement', + original: $$[$0], + value: $$[$0], + loc: yy.locInfo(this._$) + }; + + break; + case 11: + this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 12: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] }; + break; + case 13: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$); + break; + case 14: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$); + break; + case 15: + this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 16: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 17: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 18: + this.$ = { strip: yy.stripFlags($$[$0 - 1], $$[$0 - 1]), program: $$[$0] }; + break; + case 19: + var inverse = yy.prepareBlock($$[$0 - 2], $$[$0 - 1], $$[$0], $$[$0], false, this._$), + program = yy.prepareProgram([inverse], $$[$0 - 1].loc); + program.chained = true; + + this.$ = { strip: $$[$0 - 2].strip, program: program, chain: true }; + + break; + case 20: + this.$ = $$[$0]; + break; + case 21: + this.$ = { path: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 2], $$[$0]) }; + break; + case 22: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 23: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 24: + this.$ = { + type: 'PartialStatement', + name: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + indent: '', + strip: yy.stripFlags($$[$0 - 4], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 25: + this.$ = yy.preparePartialBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 26: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 4], $$[$0]) }; + break; + case 27: + this.$ = $$[$0]; + break; + case 28: + this.$ = $$[$0]; + break; + case 29: + this.$ = { + type: 'SubExpression', + path: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + loc: yy.locInfo(this._$) + }; + + break; + case 30: + this.$ = { type: 'Hash', pairs: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 31: + this.$ = { type: 'HashPair', key: yy.id($$[$0 - 2]), value: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 32: + this.$ = yy.id($$[$0 - 1]); + break; + case 33: + this.$ = $$[$0]; + break; + case 34: + this.$ = $$[$0]; + break; + case 35: + this.$ = { type: 'StringLiteral', value: $$[$0], original: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 36: + this.$ = { type: 'NumberLiteral', value: Number($$[$0]), original: Number($$[$0]), loc: yy.locInfo(this._$) }; + break; + case 37: + this.$ = { type: 'BooleanLiteral', value: $$[$0] === 'true', original: $$[$0] === 'true', loc: yy.locInfo(this._$) }; + break; + case 38: + this.$ = { type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(this._$) }; + break; + case 39: + this.$ = { type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(this._$) }; + break; + case 40: + this.$ = $$[$0]; + break; + case 41: + this.$ = $$[$0]; + break; + case 42: + this.$ = yy.preparePath(true, $$[$0], this._$); + break; + case 43: + this.$ = yy.preparePath(false, $$[$0], this._$); + break; + case 44: + $$[$0 - 2].push({ part: yy.id($$[$0]), original: $$[$0], separator: $$[$0 - 1] });this.$ = $$[$0 - 2]; + break; + case 45: + this.$ = [{ part: yy.id($$[$0]), original: $$[$0] }]; + break; + case 46: + this.$ = []; + break; + case 47: + $$[$0 - 1].push($$[$0]); + break; + case 48: + this.$ = [$$[$0]]; + break; + case 49: + $$[$0 - 1].push($$[$0]); + break; + case 50: + this.$ = []; + break; + case 51: + $$[$0 - 1].push($$[$0]); + break; + case 58: + this.$ = []; + break; + case 59: + $$[$0 - 1].push($$[$0]); + break; + case 64: + this.$ = []; + break; + case 65: + $$[$0 - 1].push($$[$0]); + break; + case 70: + this.$ = []; + break; + case 71: + $$[$0 - 1].push($$[$0]); + break; + case 78: + this.$ = []; + break; + case 79: + $$[$0 - 1].push($$[$0]); + break; + case 82: + this.$ = []; + break; + case 83: + $$[$0 - 1].push($$[$0]); + break; + case 86: + this.$ = []; + break; + case 87: + $$[$0 - 1].push($$[$0]); + break; + case 90: + this.$ = []; + break; + case 91: + $$[$0 - 1].push($$[$0]); + break; + case 94: + this.$ = []; + break; + case 95: + $$[$0 - 1].push($$[$0]); + break; + case 98: + this.$ = [$$[$0]]; + break; + case 99: + $$[$0 - 1].push($$[$0]); + break; + case 100: + this.$ = [$$[$0]]; + break; + case 101: + $$[$0 - 1].push($$[$0]); + break; + } + }, + table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 13: 40, 15: [1, 20], 17: 39 }, { 20: 42, 56: 41, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 45, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 48, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 42, 56: 49, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 50, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 51] }, { 72: [1, 35], 86: 52 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 53, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 54, 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 55, 47: [2, 54] }, { 28: 60, 43: 61, 44: [1, 59], 47: [2, 56] }, { 13: 63, 15: [1, 20], 18: [1, 62] }, { 15: [2, 48], 18: [2, 48] }, { 33: [2, 86], 57: 64, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 65, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 66, 47: [1, 67] }, { 30: 68, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 69, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 70, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 71, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 75, 33: [2, 80], 50: 72, 63: 73, 64: 76, 65: [1, 44], 69: 74, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 80] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 51] }, { 20: 75, 53: 81, 54: [2, 84], 63: 82, 64: 76, 65: [1, 44], 69: 83, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 84, 47: [1, 67] }, { 47: [2, 55] }, { 4: 85, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 86, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 87, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 88, 47: [1, 67] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 75, 33: [2, 88], 58: 89, 63: 90, 64: 76, 65: [1, 44], 69: 91, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 92, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 93, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 31: 94, 33: [2, 60], 63: 95, 64: 76, 65: [1, 44], 69: 96, 70: 77, 71: 78, 72: [1, 79], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 66], 36: 97, 63: 98, 64: 76, 65: [1, 44], 69: 99, 70: 77, 71: 78, 72: [1, 79], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 22: 100, 23: [2, 52], 63: 101, 64: 76, 65: [1, 44], 69: 102, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 92], 62: 103, 63: 104, 64: 76, 65: [1, 44], 69: 105, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 106] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 107, 72: [1, 108], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 109], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 110] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 112, 46: 111, 47: [2, 76] }, { 33: [2, 70], 40: 113, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 114] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 75, 63: 116, 64: 76, 65: [1, 44], 67: 115, 68: [2, 96], 69: 117, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 118] }, { 32: 119, 33: [2, 62], 74: 120, 75: [1, 121] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 122, 74: 123, 75: [1, 121] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 124] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 125] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 109] }, { 20: 75, 63: 126, 64: 76, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 75, 33: [2, 72], 41: 127, 63: 128, 64: 76, 65: [1, 44], 69: 129, 70: 77, 71: 78, 72: [1, 79], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 130] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 131] }, { 33: [2, 63] }, { 72: [1, 133], 76: 132 }, { 33: [1, 134] }, { 33: [2, 69] }, { 15: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 135, 74: 136, 75: [1, 121] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 138], 77: [1, 137] }, { 72: [2, 100], 77: [2, 100] }, { 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] }, { 33: [1, 139] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }], + defaultActions: { 4: [2, 1], 55: [2, 55], 57: [2, 20], 61: [2, 57], 74: [2, 81], 83: [2, 85], 87: [2, 18], 91: [2, 89], 102: [2, 53], 105: [2, 93], 111: [2, 19], 112: [2, 77], 117: [2, 97], 120: [2, 63], 123: [2, 69], 124: [2, 12], 136: [2, 75], 137: [2, 32] }, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], + lstack = [], + table = this.table, + yytext = "", + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, + preErrorSymbol, + state, + action, + a, + r, + yyval = {}, + p, + len, + newState, + expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1 ? "end of input" : "'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, { text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected }); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column }; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + /* Jison generated lexer */ + var lexer = (function () { + var lexer = { EOF: 1, + parseError: function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput: function setInput(input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 }; + if (this.options.ranges) this.yylloc.range = [0, 0]; + this.offset = 0; + return this; + }, + input: function input() { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput: function unput(ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len - 1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); + + if (lines.length - 1) this.yylineno -= lines.length - 1; + var r = this.yylloc.range; + + this.yylloc = { first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more: function more() { + this._more = true; + return this; + }, + less: function less(n) { + this.unput(this.match.slice(n)); + }, + pastInput: function pastInput() { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput: function upcomingInput() { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20 - next.length); + } + return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + showPosition: function showPosition() { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + next: function next() { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, match, tempMatch, index, col, lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = { first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index], this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) this.done = false; + if (token) return token;else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { text: "", token: null, line: this.yylineno }); + } + }, + lex: function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin: function begin(condition) { + this.conditionStack.push(condition); + }, + popState: function popState() { + return this.conditionStack.pop(); + }, + _currentRules: function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + }, + topState: function topState() { + return this.conditionStack[this.conditionStack.length - 2]; + }, + pushState: function begin(condition) { + this.begin(condition); + } }; + lexer.options = {}; + lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START + /**/) { + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng - end); + } + + var YYSTATE = YY_START; + switch ($avoiding_name_collisions) { + case 0: + if (yy_.yytext.slice(-2) === "\\\\") { + strip(0, 1); + this.begin("mu"); + } else if (yy_.yytext.slice(-1) === "\\") { + strip(0, 1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if (yy_.yytext) return 15; + + break; + case 1: + return 15; + break; + case 2: + this.popState(); + return 15; + + break; + case 3: + this.begin('raw');return 15; + break; + case 4: + this.popState(); + // Should be using `this.topState()` below, but it currently + // returns the second top instead of the first top. Opened an + // issue about it at https://github.com/zaach/jison/issues/291 + if (this.conditionStack[this.conditionStack.length - 1] === 'raw') { + return 15; + } else { + yy_.yytext = yy_.yytext.substr(5, yy_.yyleng - 9); + return 'END_RAW_BLOCK'; + } + + break; + case 5: + return 15; + break; + case 6: + this.popState(); + return 14; + + break; + case 7: + return 65; + break; + case 8: + return 68; + break; + case 9: + return 19; + break; + case 10: + this.popState(); + this.begin('raw'); + return 23; + + break; + case 11: + return 55; + break; + case 12: + return 60; + break; + case 13: + return 29; + break; + case 14: + return 47; + break; + case 15: + this.popState();return 44; + break; + case 16: + this.popState();return 44; + break; + case 17: + return 34; + break; + case 18: + return 39; + break; + case 19: + return 51; + break; + case 20: + return 48; + break; + case 21: + this.unput(yy_.yytext); + this.popState(); + this.begin('com'); + + break; + case 22: + this.popState(); + return 14; + + break; + case 23: + return 48; + break; + case 24: + return 73; + break; + case 25: + return 72; + break; + case 26: + return 72; + break; + case 27: + return 87; + break; + case 28: + // ignore whitespace + break; + case 29: + this.popState();return 54; + break; + case 30: + this.popState();return 33; + break; + case 31: + yy_.yytext = strip(1, 2).replace(/\\"/g, '"');return 80; + break; + case 32: + yy_.yytext = strip(1, 2).replace(/\\'/g, "'");return 80; + break; + case 33: + return 85; + break; + case 34: + return 82; + break; + case 35: + return 82; + break; + case 36: + return 83; + break; + case 37: + return 84; + break; + case 38: + return 81; + break; + case 39: + return 75; + break; + case 40: + return 77; + break; + case 41: + return 72; + break; + case 42: + yy_.yytext = yy_.yytext.replace(/\\([\\\]])/g, '$1');return 72; + break; + case 43: + return 'INVALID'; + break; + case 44: + return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/, /^(?:\{\{\{\{(?=[^/]))/, /^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/, /^(?:[^\x00]*?(?=(\{\{\{\{)))/, /^(?:[\s\S]*?--(~)?\}\})/, /^(?:\()/, /^(?:\))/, /^(?:\{\{\{\{)/, /^(?:\}\}\}\})/, /^(?:\{\{(~)?>)/, /^(?:\{\{(~)?#>)/, /^(?:\{\{(~)?#\*?)/, /^(?:\{\{(~)?\/)/, /^(?:\{\{(~)?\^\s*(~)?\}\})/, /^(?:\{\{(~)?\s*else\s*(~)?\}\})/, /^(?:\{\{(~)?\^)/, /^(?:\{\{(~)?\s*else\b)/, /^(?:\{\{(~)?\{)/, /^(?:\{\{(~)?&)/, /^(?:\{\{(~)?!--)/, /^(?:\{\{(~)?![\s\S]*?\}\})/, /^(?:\{\{(~)?\*?)/, /^(?:=)/, /^(?:\.\.)/, /^(?:\.(?=([=~}\s\/.)|])))/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:\}(~)?\}\})/, /^(?:(~)?\}\})/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\s)])))/, /^(?:false(?=([~}\s)])))/, /^(?:undefined(?=([~}\s)])))/, /^(?:null(?=([~}\s)])))/, /^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/, /^(?:as\s+\|)/, /^(?:\|)/, /^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/, /^(?:\[(\\\]|[^\]])*\])/, /^(?:.)/, /^(?:$)/]; + lexer.conditions = { "mu": { "rules": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "inclusive": false }, "emu": { "rules": [2], "inclusive": false }, "com": { "rules": [6], "inclusive": false }, "raw": { "rules": [3, 4, 5], "inclusive": false }, "INITIAL": { "rules": [0, 1, 44], "inclusive": true } }; + return lexer; + })(); + parser.lexer = lexer; + function Parser() { + this.yy = {}; + }Parser.prototype = parser;parser.Parser = Parser; + return new Parser(); + })();exports.__esModule = true; + exports['default'] = handlebars; + +/***/ }, +/* 24 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _visitor = __webpack_require__(25); + + var _visitor2 = _interopRequireDefault(_visitor); + + function WhitespaceControl() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + this.options = options; + } + WhitespaceControl.prototype = new _visitor2['default'](); + + WhitespaceControl.prototype.Program = function (program) { + var doStandalone = !this.options.ignoreStandalone; + + var isRoot = !this.isRootSeen; + this.isRootSeen = true; + + var body = program.body; + for (var i = 0, l = body.length; i < l; i++) { + var current = body[i], + strip = this.accept(current); + + if (!strip) { + continue; + } + + var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), + _isNextWhitespace = isNextWhitespace(body, i, isRoot), + openStandalone = strip.openStandalone && _isPrevWhitespace, + closeStandalone = strip.closeStandalone && _isNextWhitespace, + inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; + + if (strip.close) { + omitRight(body, i, true); + } + if (strip.open) { + omitLeft(body, i, true); + } + + if (doStandalone && inlineStandalone) { + omitRight(body, i); + + if (omitLeft(body, i)) { + // If we are on a standalone node, save the indent info for partials + if (current.type === 'PartialStatement') { + // Pull out the whitespace from the final line + current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; + } + } + } + if (doStandalone && openStandalone) { + omitRight((current.program || current.inverse).body); + + // Strip out the previous content node if it's whitespace only + omitLeft(body, i); + } + if (doStandalone && closeStandalone) { + // Always strip the next node + omitRight(body, i); + + omitLeft((current.inverse || current.program).body); + } + } + + return program; + }; + + WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function (block) { + this.accept(block.program); + this.accept(block.inverse); + + // Find the inverse program that is involed with whitespace stripping. + var program = block.program || block.inverse, + inverse = block.program && block.inverse, + firstInverse = inverse, + lastInverse = inverse; + + if (inverse && inverse.chained) { + firstInverse = inverse.body[0].program; + + // Walk the inverse chain to find the last inverse that is actually in the chain. + while (lastInverse.chained) { + lastInverse = lastInverse.body[lastInverse.body.length - 1].program; + } + } + + var strip = { + open: block.openStrip.open, + close: block.closeStrip.close, + + // Determine the standalone candiacy. Basically flag our content as being possibly standalone + // so our parent can determine if we actually are standalone + openStandalone: isNextWhitespace(program.body), + closeStandalone: isPrevWhitespace((firstInverse || program).body) + }; + + if (block.openStrip.close) { + omitRight(program.body, null, true); + } + + if (inverse) { + var inverseStrip = block.inverseStrip; + + if (inverseStrip.open) { + omitLeft(program.body, null, true); + } + + if (inverseStrip.close) { + omitRight(firstInverse.body, null, true); + } + if (block.closeStrip.open) { + omitLeft(lastInverse.body, null, true); + } + + // Find standalone else statments + if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) { + omitLeft(program.body); + omitRight(firstInverse.body); + } + } else if (block.closeStrip.open) { + omitLeft(program.body, null, true); + } + + return strip; + }; + + WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function (mustache) { + return mustache.strip; + }; + + WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function (node) { + /* istanbul ignore next */ + var strip = node.strip || {}; + return { + inlineStandalone: true, + open: strip.open, + close: strip.close + }; + }; + + function isPrevWhitespace(body, i, isRoot) { + if (i === undefined) { + i = body.length; + } + + // Nodes that end with newlines are considered whitespace (but are special + // cased for strip operations) + var prev = body[i - 1], + sibling = body[i - 2]; + if (!prev) { + return isRoot; + } + + if (prev.type === 'ContentStatement') { + return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(prev.original); + } + } + function isNextWhitespace(body, i, isRoot) { + if (i === undefined) { + i = -1; + } + + var next = body[i + 1], + sibling = body[i + 2]; + if (!next) { + return isRoot; + } + + if (next.type === 'ContentStatement') { + return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(next.original); + } + } + + // Marks the node to the right of the position as omitted. + // I.e. {{foo}}' ' will mark the ' ' node as omitted. + // + // If i is undefined, then the first child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitRight(body, i, multiple) { + var current = body[i == null ? 0 : i + 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) { + return; + } + + var original = current.value; + current.value = current.value.replace(multiple ? /^\s+/ : /^[ \t]*\r?\n?/, ''); + current.rightStripped = current.value !== original; + } + + // Marks the node to the left of the position as omitted. + // I.e. ' '{{foo}} will mark the ' ' node as omitted. + // + // If i is undefined then the last child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitLeft(body, i, multiple) { + var current = body[i == null ? body.length - 1 : i - 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) { + return; + } + + // We omit the last node if it's whitespace only and not preceeded by a non-content node. + var original = current.value; + current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); + current.leftStripped = current.value !== original; + return current.leftStripped; + } + + exports['default'] = WhitespaceControl; + module.exports = exports['default']; + +/***/ }, +/* 25 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function Visitor() { + this.parents = []; + } + + Visitor.prototype = { + constructor: Visitor, + mutating: false, + + // Visits a given value. If mutating, will replace the value if necessary. + acceptKey: function acceptKey(node, name) { + var value = this.accept(node[name]); + if (this.mutating) { + // Hacky sanity check: This may have a few false positives for type for the helper + // methods but will generally do the right thing without a lot of overhead. + if (value && !Visitor.prototype[value.type]) { + throw new _exception2['default']('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); + } + node[name] = value; + } + }, + + // Performs an accept operation with added sanity check to ensure + // required keys are not removed. + acceptRequired: function acceptRequired(node, name) { + this.acceptKey(node, name); + + if (!node[name]) { + throw new _exception2['default'](node.type + ' requires ' + name); + } + }, + + // Traverses a given array. If mutating, empty respnses will be removed + // for child elements. + acceptArray: function acceptArray(array) { + for (var i = 0, l = array.length; i < l; i++) { + this.acceptKey(array, i); + + if (!array[i]) { + array.splice(i, 1); + i--; + l--; + } + } + }, + + accept: function accept(object) { + if (!object) { + return; + } + + /* istanbul ignore next: Sanity code */ + if (!this[object.type]) { + throw new _exception2['default']('Unknown type: ' + object.type, object); + } + + if (this.current) { + this.parents.unshift(this.current); + } + this.current = object; + + var ret = this[object.type](object); + + this.current = this.parents.shift(); + + if (!this.mutating || ret) { + return ret; + } else if (ret !== false) { + return object; + } + }, + + Program: function Program(program) { + this.acceptArray(program.body); + }, + + MustacheStatement: visitSubExpression, + Decorator: visitSubExpression, + + BlockStatement: visitBlock, + DecoratorBlock: visitBlock, + + PartialStatement: visitPartial, + PartialBlockStatement: function PartialBlockStatement(partial) { + visitPartial.call(this, partial); + + this.acceptKey(partial, 'program'); + }, + + ContentStatement: function ContentStatement() /* content */{}, + CommentStatement: function CommentStatement() /* comment */{}, + + SubExpression: visitSubExpression, + + PathExpression: function PathExpression() /* path */{}, + + StringLiteral: function StringLiteral() /* string */{}, + NumberLiteral: function NumberLiteral() /* number */{}, + BooleanLiteral: function BooleanLiteral() /* bool */{}, + UndefinedLiteral: function UndefinedLiteral() /* literal */{}, + NullLiteral: function NullLiteral() /* literal */{}, + + Hash: function Hash(hash) { + this.acceptArray(hash.pairs); + }, + HashPair: function HashPair(pair) { + this.acceptRequired(pair, 'value'); + } + }; + + function visitSubExpression(mustache) { + this.acceptRequired(mustache, 'path'); + this.acceptArray(mustache.params); + this.acceptKey(mustache, 'hash'); + } + function visitBlock(block) { + visitSubExpression.call(this, block); + + this.acceptKey(block, 'program'); + this.acceptKey(block, 'inverse'); + } + function visitPartial(partial) { + this.acceptRequired(partial, 'name'); + this.acceptArray(partial.params); + this.acceptKey(partial, 'hash'); + } + + exports['default'] = Visitor; + module.exports = exports['default']; + +/***/ }, +/* 26 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.SourceLocation = SourceLocation; + exports.id = id; + exports.stripFlags = stripFlags; + exports.stripComment = stripComment; + exports.preparePath = preparePath; + exports.prepareMustache = prepareMustache; + exports.prepareRawBlock = prepareRawBlock; + exports.prepareBlock = prepareBlock; + exports.prepareProgram = prepareProgram; + exports.preparePartialBlock = preparePartialBlock; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function validateClose(open, close) { + close = close.path ? close.path.original : close; + + if (open.path.original !== close) { + var errorNode = { loc: open.path.loc }; + + throw new _exception2['default'](open.path.original + " doesn't match " + close, errorNode); + } + } + + function SourceLocation(source, locInfo) { + this.source = source; + this.start = { + line: locInfo.first_line, + column: locInfo.first_column + }; + this.end = { + line: locInfo.last_line, + column: locInfo.last_column + }; + } + + function id(token) { + if (/^\[.*\]$/.test(token)) { + return token.substr(1, token.length - 2); + } else { + return token; + } + } + + function stripFlags(open, close) { + return { + open: open.charAt(2) === '~', + close: close.charAt(close.length - 3) === '~' + }; + } + + function stripComment(comment) { + return comment.replace(/^\{\{~?\!-?-?/, '').replace(/-?-?~?\}\}$/, ''); + } + + function preparePath(data, parts, loc) { + loc = this.locInfo(loc); + + var original = data ? '@' : '', + dig = [], + depth = 0, + depthString = ''; + + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i].part, + + // If we have [] syntax then we do not treat path references as operators, + // i.e. foo.[this] resolves to approximately context.foo['this'] + isLiteral = parts[i].original !== part; + original += (parts[i].separator || '') + part; + + if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { + if (dig.length > 0) { + throw new _exception2['default']('Invalid path: ' + original, { loc: loc }); + } else if (part === '..') { + depth++; + depthString += '../'; + } + } else { + dig.push(part); + } + } + + return { + type: 'PathExpression', + data: data, + depth: depth, + parts: dig, + original: original, + loc: loc + }; + } + + function prepareMustache(path, params, hash, open, strip, locInfo) { + // Must use charAt to support IE pre-10 + var escapeFlag = open.charAt(3) || open.charAt(2), + escaped = escapeFlag !== '{' && escapeFlag !== '&'; + + var decorator = /\*/.test(open); + return { + type: decorator ? 'Decorator' : 'MustacheStatement', + path: path, + params: params, + hash: hash, + escaped: escaped, + strip: strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareRawBlock(openRawBlock, contents, close, locInfo) { + validateClose(openRawBlock, close); + + locInfo = this.locInfo(locInfo); + var program = { + type: 'Program', + body: contents, + strip: {}, + loc: locInfo + }; + + return { + type: 'BlockStatement', + path: openRawBlock.path, + params: openRawBlock.params, + hash: openRawBlock.hash, + program: program, + openStrip: {}, + inverseStrip: {}, + closeStrip: {}, + loc: locInfo + }; + } + + function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { + if (close && close.path) { + validateClose(openBlock, close); + } + + var decorator = /\*/.test(openBlock.open); + + program.blockParams = openBlock.blockParams; + + var inverse = undefined, + inverseStrip = undefined; + + if (inverseAndProgram) { + if (decorator) { + throw new _exception2['default']('Unexpected inverse block on decorator', inverseAndProgram); + } + + if (inverseAndProgram.chain) { + inverseAndProgram.program.body[0].closeStrip = close.strip; + } + + inverseStrip = inverseAndProgram.strip; + inverse = inverseAndProgram.program; + } + + if (inverted) { + inverted = inverse; + inverse = program; + program = inverted; + } + + return { + type: decorator ? 'DecoratorBlock' : 'BlockStatement', + path: openBlock.path, + params: openBlock.params, + hash: openBlock.hash, + program: program, + inverse: inverse, + openStrip: openBlock.strip, + inverseStrip: inverseStrip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareProgram(statements, loc) { + if (!loc && statements.length) { + var firstLoc = statements[0].loc, + lastLoc = statements[statements.length - 1].loc; + + /* istanbul ignore else */ + if (firstLoc && lastLoc) { + loc = { + source: firstLoc.source, + start: { + line: firstLoc.start.line, + column: firstLoc.start.column + }, + end: { + line: lastLoc.end.line, + column: lastLoc.end.column + } + }; + } + } + + return { + type: 'Program', + body: statements, + strip: {}, + loc: loc + }; + } + + function preparePartialBlock(open, program, close, locInfo) { + validateClose(open, close); + + return { + type: 'PartialBlockStatement', + name: open.path, + params: open.params, + hash: open.hash, + program: program, + openStrip: open.strip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + +/***/ }, +/* 27 */ +/***/ function(module, exports, __webpack_require__) { + + /* eslint-disable new-cap */ + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.Compiler = Compiler; + exports.precompile = precompile; + exports.compile = compile; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _ast = __webpack_require__(21); + + var _ast2 = _interopRequireDefault(_ast); + + var slice = [].slice; + + function Compiler() {} + + // the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + equals: function equals(other) { + var len = this.opcodes.length; + if (other.opcodes.length !== len) { + return false; + } + + for (var i = 0; i < len; i++) { + var opcode = this.opcodes[i], + otherOpcode = other.opcodes[i]; + if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) { + return false; + } + } + + // We know that length is the same between the two arrays because they are directly tied + // to the opcode behavior above. + len = this.children.length; + for (var i = 0; i < len; i++) { + if (!this.children[i].equals(other.children[i])) { + return false; + } + } + + return true; + }, + + guid: 0, + + compile: function compile(program, options) { + this.sourceNode = []; + this.opcodes = []; + this.children = []; + this.options = options; + this.stringParams = options.stringParams; + this.trackIds = options.trackIds; + + options.blockParams = options.blockParams || []; + + // These changes will propagate to the other compiler components + var knownHelpers = options.knownHelpers; + options.knownHelpers = { + 'helperMissing': true, + 'blockHelperMissing': true, + 'each': true, + 'if': true, + 'unless': true, + 'with': true, + 'log': true, + 'lookup': true + }; + if (knownHelpers) { + for (var _name in knownHelpers) { + /* istanbul ignore else */ + if (_name in knownHelpers) { + options.knownHelpers[_name] = knownHelpers[_name]; + } + } + } + + return this.accept(program); + }, + + compileProgram: function compileProgram(program) { + var childCompiler = new this.compiler(), + // eslint-disable-line new-cap + result = childCompiler.compile(program, this.options), + guid = this.guid++; + + this.usePartial = this.usePartial || result.usePartial; + + this.children[guid] = result; + this.useDepths = this.useDepths || result.useDepths; + + return guid; + }, + + accept: function accept(node) { + /* istanbul ignore next: Sanity code */ + if (!this[node.type]) { + throw new _exception2['default']('Unknown type: ' + node.type, node); + } + + this.sourceNode.unshift(node); + var ret = this[node.type](node); + this.sourceNode.shift(); + return ret; + }, + + Program: function Program(program) { + this.options.blockParams.unshift(program.blockParams); + + var body = program.body, + bodyLength = body.length; + for (var i = 0; i < bodyLength; i++) { + this.accept(body[i]); + } + + this.options.blockParams.shift(); + + this.isSimple = bodyLength === 1; + this.blockParams = program.blockParams ? program.blockParams.length : 0; + + return this; + }, + + BlockStatement: function BlockStatement(block) { + transformLiteralToPath(block); + + var program = block.program, + inverse = block.inverse; + + program = program && this.compileProgram(program); + inverse = inverse && this.compileProgram(inverse); + + var type = this.classifySexpr(block); + + if (type === 'helper') { + this.helperSexpr(block, program, inverse); + } else if (type === 'simple') { + this.simpleSexpr(block); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('blockValue', block.path.original); + } else { + this.ambiguousSexpr(block, program, inverse); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('ambiguousBlockValue'); + } + + this.opcode('append'); + }, + + DecoratorBlock: function DecoratorBlock(decorator) { + var program = decorator.program && this.compileProgram(decorator.program); + var params = this.setupFullMustacheParams(decorator, program, undefined), + path = decorator.path; + + this.useDecorators = true; + this.opcode('registerDecorator', params.length, path.original); + }, + + PartialStatement: function PartialStatement(partial) { + this.usePartial = true; + + var program = partial.program; + if (program) { + program = this.compileProgram(partial.program); + } + + var params = partial.params; + if (params.length > 1) { + throw new _exception2['default']('Unsupported number of partial arguments: ' + params.length, partial); + } else if (!params.length) { + if (this.options.explicitPartialContext) { + this.opcode('pushLiteral', 'undefined'); + } else { + params.push({ type: 'PathExpression', parts: [], depth: 0 }); + } + } + + var partialName = partial.name.original, + isDynamic = partial.name.type === 'SubExpression'; + if (isDynamic) { + this.accept(partial.name); + } + + this.setupFullMustacheParams(partial, program, undefined, true); + + var indent = partial.indent || ''; + if (this.options.preventIndent && indent) { + this.opcode('appendContent', indent); + indent = ''; + } + + this.opcode('invokePartial', isDynamic, partialName, indent); + this.opcode('append'); + }, + PartialBlockStatement: function PartialBlockStatement(partialBlock) { + this.PartialStatement(partialBlock); + }, + + MustacheStatement: function MustacheStatement(mustache) { + this.SubExpression(mustache); + + if (mustache.escaped && !this.options.noEscape) { + this.opcode('appendEscaped'); + } else { + this.opcode('append'); + } + }, + Decorator: function Decorator(decorator) { + this.DecoratorBlock(decorator); + }, + + ContentStatement: function ContentStatement(content) { + if (content.value) { + this.opcode('appendContent', content.value); + } + }, + + CommentStatement: function CommentStatement() {}, + + SubExpression: function SubExpression(sexpr) { + transformLiteralToPath(sexpr); + var type = this.classifySexpr(sexpr); + + if (type === 'simple') { + this.simpleSexpr(sexpr); + } else if (type === 'helper') { + this.helperSexpr(sexpr); + } else { + this.ambiguousSexpr(sexpr); + } + }, + ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) { + var path = sexpr.path, + name = path.parts[0], + isBlock = program != null || inverse != null; + + this.opcode('getContext', path.depth); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + path.strict = true; + this.accept(path); + + this.opcode('invokeAmbiguous', name, isBlock); + }, + + simpleSexpr: function simpleSexpr(sexpr) { + var path = sexpr.path; + path.strict = true; + this.accept(path); + this.opcode('resolvePossibleLambda'); + }, + + helperSexpr: function helperSexpr(sexpr, program, inverse) { + var params = this.setupFullMustacheParams(sexpr, program, inverse), + path = sexpr.path, + name = path.parts[0]; + + if (this.options.knownHelpers[name]) { + this.opcode('invokeKnownHelper', params.length, name); + } else if (this.options.knownHelpersOnly) { + throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr); + } else { + path.strict = true; + path.falsy = true; + + this.accept(path); + this.opcode('invokeHelper', params.length, path.original, _ast2['default'].helpers.simpleId(path)); + } + }, + + PathExpression: function PathExpression(path) { + this.addDepth(path.depth); + this.opcode('getContext', path.depth); + + var name = path.parts[0], + scoped = _ast2['default'].helpers.scopedId(path), + blockParamId = !path.depth && !scoped && this.blockParamIndex(name); + + if (blockParamId) { + this.opcode('lookupBlockParam', blockParamId, path.parts); + } else if (!name) { + // Context reference, i.e. `{{foo .}}` or `{{foo ..}}` + this.opcode('pushContext'); + } else if (path.data) { + this.options.data = true; + this.opcode('lookupData', path.depth, path.parts, path.strict); + } else { + this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped); + } + }, + + StringLiteral: function StringLiteral(string) { + this.opcode('pushString', string.value); + }, + + NumberLiteral: function NumberLiteral(number) { + this.opcode('pushLiteral', number.value); + }, + + BooleanLiteral: function BooleanLiteral(bool) { + this.opcode('pushLiteral', bool.value); + }, + + UndefinedLiteral: function UndefinedLiteral() { + this.opcode('pushLiteral', 'undefined'); + }, + + NullLiteral: function NullLiteral() { + this.opcode('pushLiteral', 'null'); + }, + + Hash: function Hash(hash) { + var pairs = hash.pairs, + i = 0, + l = pairs.length; + + this.opcode('pushHash'); + + for (; i < l; i++) { + this.pushParam(pairs[i].value); + } + while (i--) { + this.opcode('assignToHash', pairs[i].key); + } + this.opcode('popHash'); + }, + + // HELPERS + opcode: function opcode(name) { + this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc }); + }, + + addDepth: function addDepth(depth) { + if (!depth) { + return; + } + + this.useDepths = true; + }, + + classifySexpr: function classifySexpr(sexpr) { + var isSimple = _ast2['default'].helpers.simpleId(sexpr.path); + + var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]); + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var isHelper = !isBlockParam && _ast2['default'].helpers.helperExpression(sexpr); + + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. + var isEligible = !isBlockParam && (isHelper || isSimple); + + // if ambiguous, we can possibly resolve the ambiguity now + // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc. + if (isEligible && !isHelper) { + var _name2 = sexpr.path.parts[0], + options = this.options; + + if (options.knownHelpers[_name2]) { + isHelper = true; + } else if (options.knownHelpersOnly) { + isEligible = false; + } + } + + if (isHelper) { + return 'helper'; + } else if (isEligible) { + return 'ambiguous'; + } else { + return 'simple'; + } + }, + + pushParams: function pushParams(params) { + for (var i = 0, l = params.length; i < l; i++) { + this.pushParam(params[i]); + } + }, + + pushParam: function pushParam(val) { + var value = val.value != null ? val.value : val.original || ''; + + if (this.stringParams) { + if (value.replace) { + value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.'); + } + + if (val.depth) { + this.addDepth(val.depth); + } + this.opcode('getContext', val.depth || 0); + this.opcode('pushStringParam', value, val.type); + + if (val.type === 'SubExpression') { + // SubExpressions get evaluated and passed in + // in string params mode. + this.accept(val); + } + } else { + if (this.trackIds) { + var blockParamIndex = undefined; + if (val.parts && !_ast2['default'].helpers.scopedId(val) && !val.depth) { + blockParamIndex = this.blockParamIndex(val.parts[0]); + } + if (blockParamIndex) { + var blockParamChild = val.parts.slice(1).join('.'); + this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild); + } else { + value = val.original || value; + if (value.replace) { + value = value.replace(/^this(?:\.|$)/, '').replace(/^\.\//, '').replace(/^\.$/, ''); + } + + this.opcode('pushId', val.type, value); + } + } + this.accept(val); + } + }, + + setupFullMustacheParams: function setupFullMustacheParams(sexpr, program, inverse, omitEmpty) { + var params = sexpr.params; + this.pushParams(params); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + if (sexpr.hash) { + this.accept(sexpr.hash); + } else { + this.opcode('emptyHash', omitEmpty); + } + + return params; + }, + + blockParamIndex: function blockParamIndex(name) { + for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) { + var blockParams = this.options.blockParams[depth], + param = blockParams && _utils.indexOf(blockParams, name); + if (blockParams && param >= 0) { + return [depth, param]; + } + } + } + }; + + function precompile(input, options, env) { + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input); + } + + options = options || {}; + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options); + return new env.JavaScriptCompiler().compile(environment, options); + } + + function compile(input, options, env) { + if (options === undefined) options = {}; + + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input); + } + + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var compiled = undefined; + + function compileInput() { + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options), + templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true); + return env.template(templateSpec); + } + + // Template is only compiled on first use and cached after that point. + function ret(context, execOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled.call(this, context, execOptions); + } + ret._setup = function (setupOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._setup(setupOptions); + }; + ret._child = function (i, data, blockParams, depths) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._child(i, data, blockParams, depths); + }; + return ret; + } + + function argEquals(a, b) { + if (a === b) { + return true; + } + + if (_utils.isArray(a) && _utils.isArray(b) && a.length === b.length) { + for (var i = 0; i < a.length; i++) { + if (!argEquals(a[i], b[i])) { + return false; + } + } + return true; + } + } + + function transformLiteralToPath(sexpr) { + if (!sexpr.path.parts) { + var literal = sexpr.path; + // Casting to string here to make false and 0 literal values play nicely with the rest + // of the system. + sexpr.path = { + type: 'PathExpression', + data: false, + depth: 0, + parts: [literal.original + ''], + original: literal.original + '', + loc: literal.loc + }; + } + } + +/***/ }, +/* 28 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _base = __webpack_require__(4); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _codeGen = __webpack_require__(29); + + var _codeGen2 = _interopRequireDefault(_codeGen); + + function Literal(value) { + this.value = value; + } + + function JavaScriptCompiler() {} + + JavaScriptCompiler.prototype = { + // PUBLIC API: You can override these methods in a subclass to provide + // alternative compiled forms for name lookup and buffering semantics + nameLookup: function nameLookup(parent, name /* , type*/) { + if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { + return [parent, '.', name]; + } else { + return [parent, '[', JSON.stringify(name), ']']; + } + }, + depthedLookup: function depthedLookup(name) { + return [this.aliasable('container.lookup'), '(depths, "', name, '")']; + }, + + compilerInfo: function compilerInfo() { + var revision = _base.COMPILER_REVISION, + versions = _base.REVISION_CHANGES[revision]; + return [revision, versions]; + }, + + appendToBuffer: function appendToBuffer(source, location, explicit) { + // Force a source as this simplifies the merge logic. + if (!_utils.isArray(source)) { + source = [source]; + } + source = this.source.wrap(source, location); + + if (this.environment.isSimple) { + return ['return ', source, ';']; + } else if (explicit) { + // This is a case where the buffer operation occurs as a child of another + // construct, generally braces. We have to explicitly output these buffer + // operations to ensure that the emitted code goes in the correct location. + return ['buffer += ', source, ';']; + } else { + source.appendToBuffer = true; + return source; + } + }, + + initializeBuffer: function initializeBuffer() { + return this.quotedString(''); + }, + // END PUBLIC API + + compile: function compile(environment, options, context, asObject) { + this.environment = environment; + this.options = options; + this.stringParams = this.options.stringParams; + this.trackIds = this.options.trackIds; + this.precompile = !asObject; + + this.name = this.environment.name; + this.isChild = !!context; + this.context = context || { + decorators: [], + programs: [], + environments: [] + }; + + this.preamble(); + + this.stackSlot = 0; + this.stackVars = []; + this.aliases = {}; + this.registers = { list: [] }; + this.hashes = []; + this.compileStack = []; + this.inlineStack = []; + this.blockParams = []; + + this.compileChildren(environment, options); + + this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat; + this.useBlockParams = this.useBlockParams || environment.useBlockParams; + + var opcodes = environment.opcodes, + opcode = undefined, + firstLoc = undefined, + i = undefined, + l = undefined; + + for (i = 0, l = opcodes.length; i < l; i++) { + opcode = opcodes[i]; + + this.source.currentLocation = opcode.loc; + firstLoc = firstLoc || opcode.loc; + this[opcode.opcode].apply(this, opcode.args); + } + + // Flush any trailing content that might be pending. + this.source.currentLocation = firstLoc; + this.pushSource(''); + + /* istanbul ignore next */ + if (this.stackSlot || this.inlineStack.length || this.compileStack.length) { + throw new _exception2['default']('Compile completed with content left on stack'); + } + + if (!this.decorators.isEmpty()) { + this.useDecorators = true; + + this.decorators.prepend('var decorators = container.decorators;\n'); + this.decorators.push('return fn;'); + + if (asObject) { + this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]); + } else { + this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n'); + this.decorators.push('}\n'); + this.decorators = this.decorators.merge(); + } + } else { + this.decorators = undefined; + } + + var fn = this.createFunctionContext(asObject); + if (!this.isChild) { + var ret = { + compiler: this.compilerInfo(), + main: fn + }; + + if (this.decorators) { + ret.main_d = this.decorators; // eslint-disable-line camelcase + ret.useDecorators = true; + } + + var _context = this.context; + var programs = _context.programs; + var decorators = _context.decorators; + + for (i = 0, l = programs.length; i < l; i++) { + if (programs[i]) { + ret[i] = programs[i]; + if (decorators[i]) { + ret[i + '_d'] = decorators[i]; + ret.useDecorators = true; + } + } + } + + if (this.environment.usePartial) { + ret.usePartial = true; + } + if (this.options.data) { + ret.useData = true; + } + if (this.useDepths) { + ret.useDepths = true; + } + if (this.useBlockParams) { + ret.useBlockParams = true; + } + if (this.options.compat) { + ret.compat = true; + } + + if (!asObject) { + ret.compiler = JSON.stringify(ret.compiler); + + this.source.currentLocation = { start: { line: 1, column: 0 } }; + ret = this.objectLiteral(ret); + + if (options.srcName) { + ret = ret.toStringWithSourceMap({ file: options.destName }); + ret.map = ret.map && ret.map.toString(); + } else { + ret = ret.toString(); + } + } else { + ret.compilerOptions = this.options; + } + + return ret; + } else { + return fn; + } + }, + + preamble: function preamble() { + // track the last context pushed into place to allow skipping the + // getContext opcode when it would be a noop + this.lastContext = 0; + this.source = new _codeGen2['default'](this.options.srcName); + this.decorators = new _codeGen2['default'](this.options.srcName); + }, + + createFunctionContext: function createFunctionContext(asObject) { + var varDeclarations = ''; + + var locals = this.stackVars.concat(this.registers.list); + if (locals.length > 0) { + varDeclarations += ', ' + locals.join(', '); + } + + // Generate minimizer alias mappings + // + // When using true SourceNodes, this will update all references to the given alias + // as the source nodes are reused in situ. For the non-source node compilation mode, + // aliases will not be used, but this case is already being run on the client and + // we aren't concern about minimizing the template size. + var aliasCount = 0; + for (var alias in this.aliases) { + // eslint-disable-line guard-for-in + var node = this.aliases[alias]; + + if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) { + varDeclarations += ', alias' + ++aliasCount + '=' + alias; + node.children[0] = 'alias' + aliasCount; + } + } + + var params = ['container', 'depth0', 'helpers', 'partials', 'data']; + + if (this.useBlockParams || this.useDepths) { + params.push('blockParams'); + } + if (this.useDepths) { + params.push('depths'); + } + + // Perform a second pass over the output to merge content when possible + var source = this.mergeSource(varDeclarations); + + if (asObject) { + params.push(source); + + return Function.apply(this, params); + } else { + return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']); + } + }, + mergeSource: function mergeSource(varDeclarations) { + var isSimple = this.environment.isSimple, + appendOnly = !this.forceBuffer, + appendFirst = undefined, + sourceSeen = undefined, + bufferStart = undefined, + bufferEnd = undefined; + this.source.each(function (line) { + if (line.appendToBuffer) { + if (bufferStart) { + line.prepend(' + '); + } else { + bufferStart = line; + } + bufferEnd = line; + } else { + if (bufferStart) { + if (!sourceSeen) { + appendFirst = true; + } else { + bufferStart.prepend('buffer += '); + } + bufferEnd.add(';'); + bufferStart = bufferEnd = undefined; + } + + sourceSeen = true; + if (!isSimple) { + appendOnly = false; + } + } + }); + + if (appendOnly) { + if (bufferStart) { + bufferStart.prepend('return '); + bufferEnd.add(';'); + } else if (!sourceSeen) { + this.source.push('return "";'); + } + } else { + varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer()); + + if (bufferStart) { + bufferStart.prepend('return buffer + '); + bufferEnd.add(';'); + } else { + this.source.push('return buffer;'); + } + } + + if (varDeclarations) { + this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')); + } + + return this.source.merge(); + }, + + // [blockValue] + // + // On stack, before: hash, inverse, program, value + // On stack, after: return value of blockHelperMissing + // + // The purpose of this opcode is to take a block of the form + // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and + // replace it on the stack with the result of properly + // invoking blockHelperMissing. + blockValue: function blockValue(name) { + var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs(name, 0, params); + + var blockName = this.popStack(); + params.splice(1, 0, blockName); + + this.push(this.source.functionCall(blockHelperMissing, 'call', params)); + }, + + // [ambiguousBlockValue] + // + // On stack, before: hash, inverse, program, value + // Compiler value, before: lastHelper=value of last found helper, if any + // On stack, after, if no lastHelper: same as [blockValue] + // On stack, after, if lastHelper: value + ambiguousBlockValue: function ambiguousBlockValue() { + // We're being a bit cheeky and reusing the options value from the prior exec + var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs('', 0, params, true); + + this.flushInline(); + + var current = this.topStack(); + params.splice(1, 0, current); + + this.pushSource(['if (!', this.lastHelper, ') { ', current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), '}']); + }, + + // [appendContent] + // + // On stack, before: ... + // On stack, after: ... + // + // Appends the string value of `content` to the current buffer + appendContent: function appendContent(content) { + if (this.pendingContent) { + content = this.pendingContent + content; + } else { + this.pendingLocation = this.source.currentLocation; + } + + this.pendingContent = content; + }, + + // [append] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Coerces `value` to a String and appends it to the current buffer. + // + // If `value` is truthy, or 0, it is coerced into a string and appended + // Otherwise, the empty string is appended + append: function append() { + if (this.isInline()) { + this.replaceStack(function (current) { + return [' != null ? ', current, ' : ""']; + }); + + this.pushSource(this.appendToBuffer(this.popStack())); + } else { + var local = this.popStack(); + this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']); + if (this.environment.isSimple) { + this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']); + } + } + }, + + // [appendEscaped] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Escape `value` and append it to the buffer + appendEscaped: function appendEscaped() { + this.pushSource(this.appendToBuffer([this.aliasable('container.escapeExpression'), '(', this.popStack(), ')'])); + }, + + // [getContext] + // + // On stack, before: ... + // On stack, after: ... + // Compiler value, after: lastContext=depth + // + // Set the value of the `lastContext` compiler value to the depth + getContext: function getContext(depth) { + this.lastContext = depth; + }, + + // [pushContext] + // + // On stack, before: ... + // On stack, after: currentContext, ... + // + // Pushes the value of the current context onto the stack. + pushContext: function pushContext() { + this.pushStackLiteral(this.contextName(this.lastContext)); + }, + + // [lookupOnContext] + // + // On stack, before: ... + // On stack, after: currentContext[name], ... + // + // Looks up the value of `name` on the current context and pushes + // it onto the stack. + lookupOnContext: function lookupOnContext(parts, falsy, strict, scoped) { + var i = 0; + + if (!scoped && this.options.compat && !this.lastContext) { + // The depthed query is expected to handle the undefined logic for the root level that + // is implemented below, so we evaluate that directly in compat mode + this.push(this.depthedLookup(parts[i++])); + } else { + this.pushContext(); + } + + this.resolvePath('context', parts, i, falsy, strict); + }, + + // [lookupBlockParam] + // + // On stack, before: ... + // On stack, after: blockParam[name], ... + // + // Looks up the value of `parts` on the given block param and pushes + // it onto the stack. + lookupBlockParam: function lookupBlockParam(blockParamId, parts) { + this.useBlockParams = true; + + this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']); + this.resolvePath('context', parts, 1); + }, + + // [lookupData] + // + // On stack, before: ... + // On stack, after: data, ... + // + // Push the data lookup operator + lookupData: function lookupData(depth, parts, strict) { + if (!depth) { + this.pushStackLiteral('data'); + } else { + this.pushStackLiteral('container.data(data, ' + depth + ')'); + } + + this.resolvePath('data', parts, 0, true, strict); + }, + + resolvePath: function resolvePath(type, parts, i, falsy, strict) { + // istanbul ignore next + + var _this = this; + + if (this.options.strict || this.options.assumeObjects) { + this.push(strictLookup(this.options.strict && strict, this, parts, type)); + return; + } + + var len = parts.length; + for (; i < len; i++) { + /* eslint-disable no-loop-func */ + this.replaceStack(function (current) { + var lookup = _this.nameLookup(current, parts[i], type); + // We want to ensure that zero and false are handled properly if the context (falsy flag) + // needs to have the special handling for these values. + if (!falsy) { + return [' != null ? ', lookup, ' : ', current]; + } else { + // Otherwise we can use generic falsy handling + return [' && ', lookup]; + } + }); + /* eslint-enable no-loop-func */ + } + }, + + // [resolvePossibleLambda] + // + // On stack, before: value, ... + // On stack, after: resolved value, ... + // + // If the `value` is a lambda, replace it on the stack by + // the return value of the lambda + resolvePossibleLambda: function resolvePossibleLambda() { + this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']); + }, + + // [pushStringParam] + // + // On stack, before: ... + // On stack, after: string, currentContext, ... + // + // This opcode is designed for use in string mode, which + // provides the string value of a parameter along with its + // depth rather than resolving it immediately. + pushStringParam: function pushStringParam(string, type) { + this.pushContext(); + this.pushString(type); + + // If it's a subexpression, the string result + // will be pushed after this opcode. + if (type !== 'SubExpression') { + if (typeof string === 'string') { + this.pushString(string); + } else { + this.pushStackLiteral(string); + } + } + }, + + emptyHash: function emptyHash(omitEmpty) { + if (this.trackIds) { + this.push('{}'); // hashIds + } + if (this.stringParams) { + this.push('{}'); // hashContexts + this.push('{}'); // hashTypes + } + this.pushStackLiteral(omitEmpty ? 'undefined' : '{}'); + }, + pushHash: function pushHash() { + if (this.hash) { + this.hashes.push(this.hash); + } + this.hash = { values: [], types: [], contexts: [], ids: [] }; + }, + popHash: function popHash() { + var hash = this.hash; + this.hash = this.hashes.pop(); + + if (this.trackIds) { + this.push(this.objectLiteral(hash.ids)); + } + if (this.stringParams) { + this.push(this.objectLiteral(hash.contexts)); + this.push(this.objectLiteral(hash.types)); + } + + this.push(this.objectLiteral(hash.values)); + }, + + // [pushString] + // + // On stack, before: ... + // On stack, after: quotedString(string), ... + // + // Push a quoted version of `string` onto the stack + pushString: function pushString(string) { + this.pushStackLiteral(this.quotedString(string)); + }, + + // [pushLiteral] + // + // On stack, before: ... + // On stack, after: value, ... + // + // Pushes a value onto the stack. This operation prevents + // the compiler from creating a temporary variable to hold + // it. + pushLiteral: function pushLiteral(value) { + this.pushStackLiteral(value); + }, + + // [pushProgram] + // + // On stack, before: ... + // On stack, after: program(guid), ... + // + // Push a program expression onto the stack. This takes + // a compile-time guid and converts it into a runtime-accessible + // expression. + pushProgram: function pushProgram(guid) { + if (guid != null) { + this.pushStackLiteral(this.programExpression(guid)); + } else { + this.pushStackLiteral(null); + } + }, + + // [registerDecorator] + // + // On stack, before: hash, program, params..., ... + // On stack, after: ... + // + // Pops off the decorator's parameters, invokes the decorator, + // and inserts the decorator into the decorators list. + registerDecorator: function registerDecorator(paramSize, name) { + var foundDecorator = this.nameLookup('decorators', name, 'decorator'), + options = this.setupHelperArgs(name, paramSize); + + this.decorators.push(['fn = ', this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), ' || fn;']); + }, + + // [invokeHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // Pops off the helper's parameters, invokes the helper, + // and pushes the helper's return value onto the stack. + // + // If the helper is not found, `helperMissing` is called. + invokeHelper: function invokeHelper(paramSize, name, isSimple) { + var nonHelper = this.popStack(), + helper = this.setupHelper(paramSize, name), + simple = isSimple ? [helper.name, ' || '] : ''; + + var lookup = ['('].concat(simple, nonHelper); + if (!this.options.strict) { + lookup.push(' || ', this.aliasable('helpers.helperMissing')); + } + lookup.push(')'); + + this.push(this.source.functionCall(lookup, 'call', helper.callParams)); + }, + + // [invokeKnownHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // This operation is used when the helper is known to exist, + // so a `helperMissing` fallback is not required. + invokeKnownHelper: function invokeKnownHelper(paramSize, name) { + var helper = this.setupHelper(paramSize, name); + this.push(this.source.functionCall(helper.name, 'call', helper.callParams)); + }, + + // [invokeAmbiguous] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of disambiguation + // + // This operation is used when an expression like `{{foo}}` + // is provided, but we don't know at compile-time whether it + // is a helper or a path. + // + // This operation emits more code than the other options, + // and can be avoided by passing the `knownHelpers` and + // `knownHelpersOnly` flags at compile-time. + invokeAmbiguous: function invokeAmbiguous(name, helperCall) { + this.useRegister('helper'); + + var nonHelper = this.popStack(); + + this.emptyHash(); + var helper = this.setupHelper(0, name, helperCall); + + var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); + + var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')']; + if (!this.options.strict) { + lookup[0] = '(helper = '; + lookup.push(' != null ? helper : ', this.aliasable('helpers.helperMissing')); + } + + this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('"function"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']); + }, + + // [invokePartial] + // + // On stack, before: context, ... + // On stack after: result of partial invocation + // + // This operation pops off a context, invokes a partial with that context, + // and pushes the result of the invocation back. + invokePartial: function invokePartial(isDynamic, name, indent) { + var params = [], + options = this.setupParams(name, 1, params); + + if (isDynamic) { + name = this.popStack(); + delete options.name; + } + + if (indent) { + options.indent = JSON.stringify(indent); + } + options.helpers = 'helpers'; + options.partials = 'partials'; + options.decorators = 'container.decorators'; + + if (!isDynamic) { + params.unshift(this.nameLookup('partials', name, 'partial')); + } else { + params.unshift(name); + } + + if (this.options.compat) { + options.depths = 'depths'; + } + options = this.objectLiteral(options); + params.push(options); + + this.push(this.source.functionCall('container.invokePartial', '', params)); + }, + + // [assignToHash] + // + // On stack, before: value, ..., hash, ... + // On stack, after: ..., hash, ... + // + // Pops a value off the stack and assigns it to the current hash + assignToHash: function assignToHash(key) { + var value = this.popStack(), + context = undefined, + type = undefined, + id = undefined; + + if (this.trackIds) { + id = this.popStack(); + } + if (this.stringParams) { + type = this.popStack(); + context = this.popStack(); + } + + var hash = this.hash; + if (context) { + hash.contexts[key] = context; + } + if (type) { + hash.types[key] = type; + } + if (id) { + hash.ids[key] = id; + } + hash.values[key] = value; + }, + + pushId: function pushId(type, name, child) { + if (type === 'BlockParam') { + this.pushStackLiteral('blockParams[' + name[0] + '].path[' + name[1] + ']' + (child ? ' + ' + JSON.stringify('.' + child) : '')); + } else if (type === 'PathExpression') { + this.pushString(name); + } else if (type === 'SubExpression') { + this.pushStackLiteral('true'); + } else { + this.pushStackLiteral('null'); + } + }, + + // HELPERS + + compiler: JavaScriptCompiler, + + compileChildren: function compileChildren(environment, options) { + var children = environment.children, + child = undefined, + compiler = undefined; + + for (var i = 0, l = children.length; i < l; i++) { + child = children[i]; + compiler = new this.compiler(); // eslint-disable-line new-cap + + var index = this.matchExistingProgram(child); + + if (index == null) { + this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children + index = this.context.programs.length; + child.index = index; + child.name = 'program' + index; + this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile); + this.context.decorators[index] = compiler.decorators; + this.context.environments[index] = child; + + this.useDepths = this.useDepths || compiler.useDepths; + this.useBlockParams = this.useBlockParams || compiler.useBlockParams; + } else { + child.index = index; + child.name = 'program' + index; + + this.useDepths = this.useDepths || child.useDepths; + this.useBlockParams = this.useBlockParams || child.useBlockParams; + } + } + }, + matchExistingProgram: function matchExistingProgram(child) { + for (var i = 0, len = this.context.environments.length; i < len; i++) { + var environment = this.context.environments[i]; + if (environment && environment.equals(child)) { + return i; + } + } + }, + + programExpression: function programExpression(guid) { + var child = this.environment.children[guid], + programParams = [child.index, 'data', child.blockParams]; + + if (this.useBlockParams || this.useDepths) { + programParams.push('blockParams'); + } + if (this.useDepths) { + programParams.push('depths'); + } + + return 'container.program(' + programParams.join(', ') + ')'; + }, + + useRegister: function useRegister(name) { + if (!this.registers[name]) { + this.registers[name] = true; + this.registers.list.push(name); + } + }, + + push: function push(expr) { + if (!(expr instanceof Literal)) { + expr = this.source.wrap(expr); + } + + this.inlineStack.push(expr); + return expr; + }, + + pushStackLiteral: function pushStackLiteral(item) { + this.push(new Literal(item)); + }, + + pushSource: function pushSource(source) { + if (this.pendingContent) { + this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation)); + this.pendingContent = undefined; + } + + if (source) { + this.source.push(source); + } + }, + + replaceStack: function replaceStack(callback) { + var prefix = ['('], + stack = undefined, + createdStack = undefined, + usedLiteral = undefined; + + /* istanbul ignore next */ + if (!this.isInline()) { + throw new _exception2['default']('replaceStack on non-inline'); + } + + // We want to merge the inline statement into the replacement statement via ',' + var top = this.popStack(true); + + if (top instanceof Literal) { + // Literals do not need to be inlined + stack = [top.value]; + prefix = ['(', stack]; + usedLiteral = true; + } else { + // Get or create the current stack name for use by the inline + createdStack = true; + var _name = this.incrStack(); + + prefix = ['((', this.push(_name), ' = ', top, ')']; + stack = this.topStack(); + } + + var item = callback.call(this, stack); + + if (!usedLiteral) { + this.popStack(); + } + if (createdStack) { + this.stackSlot--; + } + this.push(prefix.concat(item, ')')); + }, + + incrStack: function incrStack() { + this.stackSlot++; + if (this.stackSlot > this.stackVars.length) { + this.stackVars.push('stack' + this.stackSlot); + } + return this.topStackName(); + }, + topStackName: function topStackName() { + return 'stack' + this.stackSlot; + }, + flushInline: function flushInline() { + var inlineStack = this.inlineStack; + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + /* istanbul ignore if */ + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + var stack = this.incrStack(); + this.pushSource([stack, ' = ', entry, ';']); + this.compileStack.push(stack); + } + } + }, + isInline: function isInline() { + return this.inlineStack.length; + }, + + popStack: function popStack(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && item instanceof Literal) { + return item.value; + } else { + if (!inline) { + /* istanbul ignore next */ + if (!this.stackSlot) { + throw new _exception2['default']('Invalid stack pop'); + } + this.stackSlot--; + } + return item; + } + }, + + topStack: function topStack() { + var stack = this.isInline() ? this.inlineStack : this.compileStack, + item = stack[stack.length - 1]; + + /* istanbul ignore if */ + if (item instanceof Literal) { + return item.value; + } else { + return item; + } + }, + + contextName: function contextName(context) { + if (this.useDepths && context) { + return 'depths[' + context + ']'; + } else { + return 'depth' + context; + } + }, + + quotedString: function quotedString(str) { + return this.source.quotedString(str); + }, + + objectLiteral: function objectLiteral(obj) { + return this.source.objectLiteral(obj); + }, + + aliasable: function aliasable(name) { + var ret = this.aliases[name]; + if (ret) { + ret.referenceCount++; + return ret; + } + + ret = this.aliases[name] = this.source.wrap(name); + ret.aliasable = true; + ret.referenceCount = 1; + + return ret; + }, + + setupHelper: function setupHelper(paramSize, name, blockHelper) { + var params = [], + paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); + var foundHelper = this.nameLookup('helpers', name, 'helper'), + callContext = this.aliasable(this.contextName(0) + ' != null ? ' + this.contextName(0) + ' : {}'); + + return { + params: params, + paramsInit: paramsInit, + name: foundHelper, + callParams: [callContext].concat(params) + }; + }, + + setupParams: function setupParams(helper, paramSize, params) { + var options = {}, + contexts = [], + types = [], + ids = [], + objectArgs = !params, + param = undefined; + + if (objectArgs) { + params = []; + } + + options.name = this.quotedString(helper); + options.hash = this.popStack(); + + if (this.trackIds) { + options.hashIds = this.popStack(); + } + if (this.stringParams) { + options.hashTypes = this.popStack(); + options.hashContexts = this.popStack(); + } + + var inverse = this.popStack(), + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + options.fn = program || 'container.noop'; + options.inverse = inverse || 'container.noop'; + } + + // The parameters go on to the stack in order (making sure that they are evaluated in order) + // so we need to pop them off the stack in reverse order + var i = paramSize; + while (i--) { + param = this.popStack(); + params[i] = param; + + if (this.trackIds) { + ids[i] = this.popStack(); + } + if (this.stringParams) { + types[i] = this.popStack(); + contexts[i] = this.popStack(); + } + } + + if (objectArgs) { + options.args = this.source.generateArray(params); + } + + if (this.trackIds) { + options.ids = this.source.generateArray(ids); + } + if (this.stringParams) { + options.types = this.source.generateArray(types); + options.contexts = this.source.generateArray(contexts); + } + + if (this.options.data) { + options.data = 'data'; + } + if (this.useBlockParams) { + options.blockParams = 'blockParams'; + } + return options; + }, + + setupHelperArgs: function setupHelperArgs(helper, paramSize, params, useRegister) { + var options = this.setupParams(helper, paramSize, params); + options = this.objectLiteral(options); + if (useRegister) { + this.useRegister('options'); + params.push('options'); + return ['options=', options]; + } else if (params) { + params.push(options); + return ''; + } else { + return options; + } + } + }; + + (function () { + var reservedWords = ('break else new var' + ' case finally return void' + ' catch for switch while' + ' continue function this with' + ' default if throw' + ' delete in try' + ' do instanceof typeof' + ' abstract enum int short' + ' boolean export interface static' + ' byte extends long super' + ' char final native synchronized' + ' class float package throws' + ' const goto private transient' + ' debugger implements protected volatile' + ' double import public let yield await' + ' null true false').split(' '); + + var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; + + for (var i = 0, l = reservedWords.length; i < l; i++) { + compilerWords[reservedWords[i]] = true; + } + })(); + + JavaScriptCompiler.isValidJavaScriptVariableName = function (name) { + return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name); + }; + + function strictLookup(requireTerminal, compiler, parts, type) { + var stack = compiler.popStack(), + i = 0, + len = parts.length; + if (requireTerminal) { + len--; + } + + for (; i < len; i++) { + stack = compiler.nameLookup(stack, parts[i], type); + } + + if (requireTerminal) { + return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')']; + } else { + return stack; + } + } + + exports['default'] = JavaScriptCompiler; + module.exports = exports['default']; + +/***/ }, +/* 29 */ +/***/ function(module, exports, __webpack_require__) { + + /* global define */ + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var SourceNode = undefined; + + try { + /* istanbul ignore next */ + if (false) { + // We don't support this in AMD environments. For these environments, we asusme that + // they are running on the browser and thus have no need for the source-map library. + var SourceMap = require('source-map'); + SourceNode = SourceMap.SourceNode; + } + } catch (err) {} + /* NOP */ + + /* istanbul ignore if: tested but not covered in istanbul due to dist build */ + if (!SourceNode) { + SourceNode = function (line, column, srcFile, chunks) { + this.src = ''; + if (chunks) { + this.add(chunks); + } + }; + /* istanbul ignore next */ + SourceNode.prototype = { + add: function add(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src += chunks; + }, + prepend: function prepend(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src = chunks + this.src; + }, + toStringWithSourceMap: function toStringWithSourceMap() { + return { code: this.toString() }; + }, + toString: function toString() { + return this.src; + } + }; + } + + function castChunk(chunk, codeGen, loc) { + if (_utils.isArray(chunk)) { + var ret = []; + + for (var i = 0, len = chunk.length; i < len; i++) { + ret.push(codeGen.wrap(chunk[i], loc)); + } + return ret; + } else if (typeof chunk === 'boolean' || typeof chunk === 'number') { + // Handle primitives that the SourceNode will throw up on + return chunk + ''; + } + return chunk; + } + + function CodeGen(srcFile) { + this.srcFile = srcFile; + this.source = []; + } + + CodeGen.prototype = { + isEmpty: function isEmpty() { + return !this.source.length; + }, + prepend: function prepend(source, loc) { + this.source.unshift(this.wrap(source, loc)); + }, + push: function push(source, loc) { + this.source.push(this.wrap(source, loc)); + }, + + merge: function merge() { + var source = this.empty(); + this.each(function (line) { + source.add([' ', line, '\n']); + }); + return source; + }, + + each: function each(iter) { + for (var i = 0, len = this.source.length; i < len; i++) { + iter(this.source[i]); + } + }, + + empty: function empty() { + var loc = this.currentLocation || { start: {} }; + return new SourceNode(loc.start.line, loc.start.column, this.srcFile); + }, + wrap: function wrap(chunk) { + var loc = arguments.length <= 1 || arguments[1] === undefined ? this.currentLocation || { start: {} } : arguments[1]; + + if (chunk instanceof SourceNode) { + return chunk; + } + + chunk = castChunk(chunk, this, loc); + + return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk); + }, + + functionCall: function functionCall(fn, type, params) { + params = this.generateList(params); + return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']); + }, + + quotedString: function quotedString(str) { + return '"' + (str + '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + objectLiteral: function objectLiteral(obj) { + var pairs = []; + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = castChunk(obj[key], this); + if (value !== 'undefined') { + pairs.push([this.quotedString(key), ':', value]); + } + } + } + + var ret = this.generateList(pairs); + ret.prepend('{'); + ret.add('}'); + return ret; + }, + + generateList: function generateList(entries) { + var ret = this.empty(); + + for (var i = 0, len = entries.length; i < len; i++) { + if (i) { + ret.add(','); + } + + ret.add(castChunk(entries[i], this)); + } + + return ret; + }, + + generateArray: function generateArray(entries) { + var ret = this.generateList(entries); + ret.prepend('['); + ret.add(']'); + + return ret; + } + }; + + exports['default'] = CodeGen; + module.exports = exports['default']; + +/***/ } +/******/ ]) +}); +; \ No newline at end of file From bcb0906b24786a1820cba8a2603f7ee6d6037d8a Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Tue, 6 Oct 2015 04:44:30 -0500 Subject: [PATCH 021/557] Only scan PHP files. --- src/wpephpcompat.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 481eee6..d1eacef 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -38,6 +38,7 @@ public function runTest($dir) $this->values['testVersion'] = $this->testVersion; $this->values['standard'] = "PHPCompatibility"; $this->values['reportWidth'] = "9999"; + $this->values['extensions'] = array("php"); ob_start(); From b172da9a7b26152b60970a5856050e86a14f6f9c Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Tue, 6 Oct 2015 04:45:16 -0500 Subject: [PATCH 022/557] Get actual plugins. --- index.php | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/index.php b/index.php index 22175d9..b887119 100644 --- a/index.php +++ b/index.php @@ -50,15 +50,36 @@ function wpephpcompat_start_test() error_log("scan status: " . $scan_status); if (!$scan_status) { - //FIXME: Queue actual plugins. - $dir = array( - 'post_title' => "test", - 'post_content' => "/vagrant/content/plugins/test", - 'post_status' => 'publish', - 'post_author' => 1, - 'post_type' => 'wpephpcompat_jobs' - ); - error_log("Insert post:" . wp_insert_post( $dir )); + //Add plugins. + //TODO: Add logic to only get active plugins. + $plugin_base = dirname(__DIR__) . DIRECTORY_SEPARATOR; + + $all_plugins = get_plugins(); + + foreach ($all_plugins as $k => $v) + { + //Exclude our plugin. + if ($v["Name"] === "WP Engine PHP Compatibility") + { + continue; + } + + $plugin_path = $plugin_base . plugin_dir_path($k); + + add_directory($v["Name"], $plugin_path); + } + + //Add themes. + //TODO: Add logic to only get active theme. + $all_themes = wp_get_themes(); + + foreach ($all_themes as $k => $v) + { + + $theme_path = $all_themes[$k]->theme_root . DIRECTORY_SEPARATOR . $k . DIRECTORY_SEPARATOR; + + add_directory($all_themes[$k]->Name, $theme_path); + } update_option($scan_status_name, "1"); } @@ -148,3 +169,16 @@ function wpephpcompat_settings_page()
$name, + 'post_content' => $path, + 'post_status' => 'publish', + 'post_author' => 1, + 'post_type' => 'wpephpcompat_jobs' + ); + + wp_insert_post( $dir ); +} From 3dfe2f8ab310e6af6a1088d86cff2964b5692c6c Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Tue, 6 Oct 2015 04:45:46 -0500 Subject: [PATCH 023/557] Clean up after a scan. --- index.php | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/index.php b/index.php index b887119..f698c0f 100644 --- a/index.php +++ b/index.php @@ -92,8 +92,7 @@ function wpephpcompat_start_test() if (!$directories) { error_log("no posts"); - delete_option($lock_name); - delete_option($scan_status_name); + clean(); return; } @@ -114,12 +113,7 @@ function wpephpcompat_start_test() echo $scan_results; //All scans finished, clean up! - delete_option($scan_status_name); - delete_option("wpephpcompat_scan_results"); - delete_option("wpephpcompat_scan_results"); - wp_clear_scheduled_hook("wpephpcompat_start_test_cron"); - - delete_option($lock_name); + clean(); wp_die(); } @@ -182,3 +176,20 @@ function add_directory($name, $path) wp_insert_post( $dir ); } + +function clean() +{ + delete_option("wpephpcompat.lock"); + delete_option("wpephpcompat.status"); + delete_option("wpephpcompat_scan_results"); + wp_clear_scheduled_hook("wpephpcompat_start_test_cron"); + + $args = array('posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs'); + + $directories = get_posts($args); + + foreach ($directories as $directory) + { + wp_delete_post($directory->ID); + } +} From 2509fef41184da95f1a9441f118d8134868aa273 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sat, 10 Oct 2015 07:30:58 -0500 Subject: [PATCH 024/557] Add our results handlebar template. --- index.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/index.php b/index.php index f698c0f..1c4f725 100644 --- a/index.php +++ b/index.php @@ -192,4 +192,19 @@ function clean() { wp_delete_post($directory->ID); } + + + Date: Sat, 10 Oct 2015 07:31:18 -0500 Subject: [PATCH 025/557] New page layout. --- index.php | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/index.php b/index.php index 1c4f725..bd8b937 100644 --- a/index.php +++ b/index.php @@ -149,13 +149,34 @@ function wpephpcompat_settings_page() ?>
-

WP Engine PHP Compatibility

-

-

WP Engine PHP Compatibility

Developer mode
+

+

Scan Settings

+ Scan only active plugins and themes?
+ Yes +
+ No +
+ PHP Version?
+ PHP 5.5 +
+ PHP 5.4 +
+ PHP 5.3 + +

+

+ +
- ?> - Test Results: - + +

From 8ff69beeb1d32a88b6851ec1e02ffe546d901417 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sat, 10 Oct 2015 07:31:39 -0500 Subject: [PATCH 026/557] Add WP-CLI support. --- index.php | 5 +++++ src/wpcli.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/wpcli.php diff --git a/index.php b/index.php index bd8b937..031cb7d 100644 --- a/index.php +++ b/index.php @@ -20,6 +20,11 @@ //Create custom post type. add_action( 'init', 'wpephpcompat_create_job_queue' ); +//Add the phpcompat WP-CLI command. +if ( defined('WP_CLI') && WP_CLI ) { + include __DIR__ . '/src/wpcli.php'; +} + function wpephpcompat_start_test() { global $wpdb; diff --git a/src/wpcli.php b/src/wpcli.php new file mode 100644 index 0000000..431dddb --- /dev/null +++ b/src/wpcli.php @@ -0,0 +1,42 @@ + + * : PHP version to test. + * + * ## EXAMPLES + * + * wp phpcompat 5.5 + * + * @synopsis + */ + function __invoke( $args, $assoc_args ) { + list( $testVersion ) = $args; + + $root_dir = realpath(__DIR__ . "/../"); + + $wpephpc = new \WPEPHPCompat($root_dir); + + $wpephpc->testVersion = $testVersion; + + $wpephpc->onlyActive = "yes"; + + $wpephpc->startTest(); + + // Print a success message + WP_CLI::success( "Test finished!" ); + } +} + +WP_CLI::add_command( 'phpcompat', 'PHPCompat_Command' ); \ No newline at end of file From c293361b3f51926997a3f1e1c3d11f45a573dbb2 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Sat, 10 Oct 2015 07:32:15 -0500 Subject: [PATCH 027/557] Remove clean and add_directory. --- index.php | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/index.php b/index.php index 031cb7d..65ab02a 100644 --- a/index.php +++ b/index.php @@ -187,37 +187,7 @@ function wpephpcompat_settings_page()

- $name, - 'post_content' => $path, - 'post_status' => 'publish', - 'post_author' => 1, - 'post_type' => 'wpephpcompat_jobs' - ); - - wp_insert_post( $dir ); -} - -function clean() -{ - delete_option("wpephpcompat.lock"); - delete_option("wpephpcompat.status"); - delete_option("wpephpcompat_scan_results"); - wp_clear_scheduled_hook("wpephpcompat_start_test_cron"); - - $args = array('posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs'); - $directories = get_posts($args); - - foreach ($directories as $directory) - { - wp_delete_post($directory->ID); - } 0) - { - errors += parseInt(m[1]); - } - } - - while ((m = warningRegex.exec(log)) !== null) { - if (m.index === warningRegex.lastIndex) { - warningRegex.lastIndex++; - } - if (parseInt(m[1]) > 0) - { - warnings += parseInt(m[1]); - } - } - - //var match = warningRegex.match(log); - - //console.log(match) - - //alert("pause") - var passed = 1; - - if (parseInt(errors) > 0) - { - compatible = 0; - passed = 0; - } - - //Use handlebars to fill our template. - var source = $("#result-template").html(); - var template = Handlebars.compile(source); - var context = {plugin_name: name, warnings: warnings, errors: errors, logs: log, passed: passed, testVersion: testVersion}; - var html = template(context); - - $("#standardMode").append(html); - - } - if (compatible) - { - $("#standardMode").prepend("

Your WordPress install is PHP " + testVersion + " compatible."); - } - else - { - $("#standardMode").prepend("

Your WordPress install is not PHP " + testVersion + " compatible."); - } - - + displayReport(response); }); }); }); + +/** + * Check the scan status and display results if scan is done. + */ +function checkStatus() +{ + var data = + { + 'action': 'wpephpcompat_check_status' + }; + + jQuery.post(ajax_object.ajax_url, data, function(response) + { + console.log(response); + + if (response !== "0") + { + displayReport(response); + } + }); +} + +/** + * Clear previous results. + */ +function resetDisplay() +{ + jQuery("#testResults").text(""); + jQuery("#standardMode").html(""); +} + +/** + * Loop through a string and count the total matches. + * @param {RegExp} regex Regex to execute. + * @param {string} log String to loop through. + * @return {int} The total number of matches. + */ +function findAll(regex, log) +{ + var m; + var count = 0; + while ((m = regex.exec(log)) !== null) + { + if (m.index === regex.lastIndex) + { + regex.lastIndex++; + } + if (parseInt(m[1]) > 0) + { + count += parseInt(m[1]); + } + } + return count; +} + +/** + * Display the pretty report. + * @param {string} response Full test results. + */ +function displayReport(response) +{ + //Clear status timer. + clearInterval(timer); + + var $ = jQuery; + var compatible = 1; + var errorsRegex = /(\d*) ERRORS?/g; + var warningRegex = /(\d*) WARNINGS?/g; + var updateVersionRegex = /e: (.*?);/g; + var currentVersionRegex = /n: (.*?);/g; + + $("#runButton").removeClass("button-primary-disabled"); + $(".spinner").hide(); + $("#testResults").text(response); + + $("#footer").show(); + + $("#runButton").val("Re-run"); + + //Separate plugins/themes. + var plugins = response.split("Name: "); + + //Loop through them. + for (var x in plugins) + { + if (plugins[x] === "") + { + continue; + } + + var updateVersion; + var updateAvailable = 0; + var passed = 1; + + //Extract plugin/theme name. + var name = plugins[x].substring(0, plugins[x].indexOf("\n")); + //Extract results. + var log = plugins[x].substring(plugins[x].indexOf("\n"), plugins[x].length); + + //Find number of errors and warnings. + var errors = findAll(errorsRegex, log); + var warnings = findAll(warningRegex, log); + + //Check to see if there are any plugin/theme updates. + if (updateVersionRegex.exec(log)) + { + updateAvailable = 1; + } + + //Update plugin and global compatibility flags. + if (parseInt(errors) > 0) + { + compatible = 0; + passed = 0; + } + + //Trim whitespace and newlines from report. + log = log.replace(/^\s+|\s+$/g, ""); + + //Use handlebars to build our template. + var source = $("#result-template").html(); + var template = Handlebars.compile(source); + var context = {plugin_name: name, warnings: warnings, errors: errors, logs: log, passed: passed, test_version: test_version, updateAvailable: updateAvailable}; + var html = template(context); + + $("#standardMode").append(html); + + } + + //Display global compatibility status. + if (compatible) + { + $("#standardMode").prepend("

Your WordPress install is PHP " + test_version + " compatible.

"); + } + else + { + $("#standardMode").prepend("

Your WordPress install is not PHP " + test_version + " compatible.

"); + } + +} \ No newline at end of file diff --git a/src/wpcli.php b/src/wpcli.php index 431dddb..0bf56e8 100644 --- a/src/wpcli.php +++ b/src/wpcli.php @@ -21,21 +21,32 @@ class PHPCompat_Command extends WP_CLI_Command { * * @synopsis */ - function __invoke( $args, $assoc_args ) { - list( $testVersion ) = $args; + function __invoke( $args, $assoc_args ) + { + list( $test_version ) = $args; + + WP_CLI::line("Testing compatibility with PHP " . $test_version . "."); $root_dir = realpath(__DIR__ . "/../"); $wpephpc = new \WPEPHPCompat($root_dir); - $wpephpc->testVersion = $testVersion; + $wpephpc->test_version = $test_version; - $wpephpc->onlyActive = "yes"; + $wpephpc->only_active = "yes"; - $wpephpc->startTest(); - - // Print a success message - WP_CLI::success( "Test finished!" ); + $results = $wpephpc->startTest(); + + echo $results; + + if (preg_match("/(\d*) ERRORS?/i", $results)) + { + WP_CLI::error( "Your WordPress install is not compatible." ); + } + else + { + WP_CLI::success( "Your WordPress install is compatible." ); + } } } diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 76c3c9d..842a227 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -20,9 +20,9 @@ class WPEPHPCompat * Version of PHP to test. * @var string */ - public $testVersion = null; + public $test_version = null; - public $onlyActive = null; + public $only_active = null; public $lock_name = 'wpephpcompat.lock'; @@ -36,12 +36,19 @@ function __construct($dir) $this->cli = new PHP_CodeSniffer_CLI(); } + /** + * Start the testing process. + * TODO: Return the results instead of echoing. + * TODO: + */ public function startTest() { - // Try to lock. + + $this->debugLog("startScan: " . isset($_POST['startScan'])); + // Try to lock. $lock_result = add_option($this->lock_name, time(), '', 'no' ); - error_log("lock: ". $lock_result); + $this->debugLog("lock: ". $lock_result); if (!$lock_result) { @@ -50,7 +57,7 @@ public function startTest() // Bail if we were unable to create a lock, or if the existing lock is still valid. if ( ! $lock_result || ( $lock_result > ( time() - MINUTE_IN_SECONDS ) ) ) { - error_log("Locked, this would have returned."); + $this->debugLog("Process already running (locked), returning."); return; } } @@ -58,24 +65,32 @@ public function startTest() //Check to see if scan has already started. $scan_status = get_option($this->scan_status_name); - error_log("scan status: " . $scan_status); + $this->debugLog("scan status: " . $scan_status); if (!$scan_status) { - //Add plugins. - //TODO: Add logic to only get active plugins. + $this->debugLog("Generating directory list."); + //Add plugins and themes. $this->generateDirectoryList(); + + add_option($this->scan_status_name, "1"); + add_option("wpephpcompat.test_version", $this->test_version); + add_option("wpephpcompat.only_active", $this->only_active); + } + else + { + //Get scan settings from database. + $this->test_version = get_option("wpephpcompat.test_version"); + $this->only_active = get_option("wpephpcompat.only_active"); } $args = array('posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs'); $directories = get_posts($args); - error_log("After getting posts."); + $this->debugLog("After getting posts."); //If there are no directories to scan, we're finished! if (!$directories) - { - error_log("no posts"); - + { $this->cleanAfterScan(); return; @@ -83,21 +98,50 @@ public function startTest() wp_schedule_single_event( time() + ( MINUTE_IN_SECONDS ), 'wpephpcompat_start_test_cron' ); - $scan_results = get_option("wpephpcompat_scan_results"); + $scan_results = get_option("wpephpcompat.scan_results"); foreach ($directories as $directory) { $report = $this->processFile($directory->post_content); - $scan_results .= "Name: " . $directory->post_title . "\n" . $report . "\n"; - update_option("wpephpcompat_scan_results", $scan_results); + + $this->debugLog("Processing: " . $directory->post_title); + + if (!$report) + { + $report = "PHP " . $this->test_version . " compatible."; + } + + $scan_results .= "Name: " . $directory->post_title . "\n\n" . $report . "\n"; + //update_post_meta($directory->ID, "results", ) + + $update = get_post_meta( $directory->ID, "update", true ); + + if (!empty($update)) + { + $version = get_post_meta( $directory->ID, "version", true ); + $scan_results .= "Update Available: " . $update . "; Current Version: " . $version . ";\n"; + } + + $scan_results .= "\n"; + + update_option("wpephpcompat.scan_results", $scan_results); + wp_delete_post($directory->ID); } - echo $scan_results; + //Only clean up if not ran from cron. + if (isset($_POST['startScan'])) + { + $this->cleanAfterScan(); + } + + update_option($this->scan_status_name, "0"); + + $this->debugLog("Scan finished."); - //All scans finished, clean up! - $this->cleanAfterScan(); + //TODO: Use json to return test_version. + return $scan_results; } /** @@ -108,12 +152,12 @@ public function processFile($dir) { $this->values['files'] = $dir; //$this->values['ignored'] = $this->generateIgnoreList(); - $this->values['testVersion'] = $this->testVersion; + $this->values['testVersion'] = $this->test_version; $this->values['standard'] = "PHPCompatibility"; $this->values['reportWidth'] = "9999"; $this->values['extensions'] = array("php"); - PHP_CodeSniffer::setConfigData('testVersion', $this->testVersion, true); + PHP_CodeSniffer::setConfigData('testVersion', $this->test_version, true); ob_start(); @@ -131,10 +175,17 @@ public function processFile($dir) */ public function generateDirectoryList() { + if ( !function_exists( 'get_plugins' ) ) + { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $plugin_base = dirname($this->base) . DIRECTORY_SEPARATOR; $all_plugins = get_plugins(); + $update_plugins = get_site_transient('update_plugins'); + foreach ($all_plugins as $k => $v) { //Exclude our plugin. @@ -143,8 +194,8 @@ public function generateDirectoryList() continue; } - //Exclude active plugins if onlyActive = "yes". - if ($this->onlyActive === "yes") + //Exclude active plugins if only_active = "yes". + if ($this->only_active === "yes") { //Get array of active plugins. $active_plugins = get_option('active_plugins'); @@ -157,16 +208,29 @@ public function generateDirectoryList() $plugin_path = $plugin_base . plugin_dir_path($k); - $this->addDirectory($v["Name"], $plugin_path); + $id = $this->addDirectory($v["Name"], $plugin_path); + + //Check for plugin updates. + foreach ($update_plugins->response as $uk => $uv) + { + //If we have a match. + if ($uk === $k) + { + $this->debugLog("An update exists for: " . $v["Name"]); + //Save the update version. + update_post_meta($id, "update", $uv->new_version); + //Save the current version. + update_post_meta($id, "version", $v["Version"]); + } + } } //Add themes. - //TODO: Add logic to only get active theme. $all_themes = wp_get_themes(); foreach ($all_themes as $k => $v) { - if ($this->onlyActive === "yes") + if ($this->only_active === "yes") { $current_theme = wp_get_theme(); if ($all_themes[$k]->Name != $current_theme->Name) @@ -177,21 +241,8 @@ public function generateDirectoryList() $this->addDirectory($all_themes[$k]->Name, $theme_path); } - - update_option($this->scan_status_name, "1"); - } - - /** - * Generate a list of files to ignore. - * @return array Array of files to exclude from the scan. - */ - private function generateIgnoreList() - { - //Get this plugins relative directory. - $pluginDir = dirname(plugin_basename(__DIR__)); - return array($pluginDir); } - + /** * Cleans and formats the final report. * @param string $report The full report. @@ -208,16 +259,23 @@ private function cleanReport($report) return $report; } + /** + * Remove all database entries created by the scan. + */ public function cleanAfterScan() - { - + { + //Delete options created during the scan. delete_option("wpephpcompat.lock"); delete_option("wpephpcompat.status"); - delete_option("wpephpcompat_scan_results"); + delete_option("wpephpcompat.scan_results"); + delete_option("wpephpcompat.test_version"); + delete_option("wpephpcompat.only_active"); + + //Clear scheduled cron. wp_clear_scheduled_hook("wpephpcompat_start_test_cron"); - $args = array('posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs'); - + //Make sure all directories are removed from the queue. + $args = array('posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs'); $directories = get_posts($args); foreach ($directories as $directory) @@ -226,6 +284,11 @@ public function cleanAfterScan() } } + /** + * Add a path to the wpephpcompat_jobs custom post type. + * @param string $name Plugin or theme name. + * @param string $path Full path to the plugin or theme directory. + */ private function addDirectory($name, $path) { $dir = array( @@ -236,7 +299,26 @@ private function addDirectory($name, $path) 'post_type' => 'wpephpcompat_jobs' ); - wp_insert_post( $dir ); + return wp_insert_post( $dir ); + } + + /** + * Log to the error log if WP_DEBUG is enabled. + * @param string $message Message to log. + */ + private function debugLog($message) + { + if (WP_DEBUG === true) + { + if (is_array($message) || is_object($message)) + { + error_log(print_r($message, true)); + } + else + { + error_log("WPE PHP Compatibility: " . $message); + } + } } } From c157e9b54a898d480b3bf2307013eb841553bb2c Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Tue, 3 Nov 2015 08:04:03 -0600 Subject: [PATCH 049/557] Prefix CSS. --- index.php | 4 ++-- src/css/style.css | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/index.php b/index.php index 23c1e05..9be6af4 100644 --- a/index.php +++ b/index.php @@ -169,7 +169,7 @@ function wpephpcompat_settings_page() Date: Tue, 3 Nov 2015 08:04:38 -0600 Subject: [PATCH 050/557] Move textarea CSS to style.css. --- index.php | 2 +- src/css/style.css | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/index.php b/index.php index 9be6af4..c7290ee 100644 --- a/index.php +++ b/index.php @@ -154,7 +154,7 @@ function wpephpcompat_settings_page() - + - Date: Mon, 20 Jun 2016 11:28:02 -0700 Subject: [PATCH 081/557] Formatting / Refactor / Nitpicking all the things --- src/wpephpcompat.php | 137 +++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index f6278be..30916b7 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -94,56 +94,59 @@ function __construct( $dir ) { */ public function start_test() { - $this->debugLog( "startScan: " . isset( $_POST['startScan'] ) ); + $this->debugLog( 'startScan: ' . isset( $_POST['startScan'] ) ); // Try to lock. - $lock_result = add_option( "wpephpcompat.lock", time(), '', 'no' ); + $lock_result = add_option( 'wpephpcompat.lock', time(), '', 'no' ); - $this->debugLog( "lock: ". $lock_result ); + $this->debugLog( 'lock: ' . $lock_result ); if ( ! $lock_result ) { - $lock_result = get_option("wpephpcompat.lock"); + $lock_result = get_option( 'wpephpcompat.lock' ); // Bail if we were unable to create a lock, or if the existing lock is still valid. if ( ! $lock_result || ( $lock_result > ( time() - MINUTE_IN_SECONDS ) ) ) { - $this->debugLog("Process already running (locked), returning."); + $this->debugLog( 'Process already running (locked), returning.' ); $timestamp = wp_next_scheduled( 'wpephpcompat_start_test_cron' ); - if( $timestamp == false ) { + if ( $timestamp == false ) { wp_schedule_single_event( time() + ( MINUTE_IN_SECONDS ), 'wpephpcompat_start_test_cron' ); } return; } } - update_option( "wpephpcompat.lock", time() ); + update_option( 'wpephpcompat.lock', time() ); // Check to see if scan has already started. - $scan_status = get_option( "wpephpcompat.status" ); - $this->debugLog( "scan status: " . $scan_status ); - if ( ! $scan_status) { - $this->debugLog("Generating directory list."); + $scan_status = get_option( 'wpephpcompat.status' ); + $this->debugLog( 'scan status: ' . $scan_status ); + if ( ! $scan_status ) { + $this->debugLog( 'Generating directory list.' ); //Add plugins and themes. $this->generateDirectoryList(); - add_option( "wpephpcompat.status", "1" ); - add_option( "wpephpcompat.test_version", $this->test_version ); - add_option( "wpephpcompat.only_active", $this->only_active ); + add_option( 'wpephpcompat.status', '1' ); + add_option( 'wpephpcompat.test_version', $this->test_version ); + add_option( 'wpephpcompat.only_active', $this->only_active ); $count_jobs = wp_count_posts( 'wpephpcompat_jobs' ); - add_option( "wpephpcompat.numdirs", $count_jobs->publish ); + add_option( 'wpephpcompat.numdirs', $count_jobs->publish ); } else { // Get scan settings from database. - $this->test_version = get_option( "wpephpcompat.test_version" ); - $this->only_active = get_option( "wpephpcompat.only_active" ); + $this->test_version = get_option( 'wpephpcompat.test_version' ); + $this->only_active = get_option( 'wpephpcompat.only_active' ); } - $args = array('posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs'); - $directories = get_posts($args); - $this->debugLog(count($directories) . " plugins left to process."); + $args = array( + 'posts_per_page' => -1, + 'post_type' => 'wpephpcompat_jobs' + ); + $directories = get_posts( $args ); + $this->debugLog( count( $directories ) . ' plugins left to process.' ); // If there are no directories to scan, we're finished! if ( ! $directories ) { - $this->debugLog("No more plugins to process."); + $this->debugLog( 'No more plugins to process.' ); $this->cleanAfterScan(); return; @@ -151,36 +154,36 @@ public function start_test() { wp_schedule_single_event( time() + ( MINUTE_IN_SECONDS ), 'wpephpcompat_start_test_cron' ); - $scan_results = get_option("wpephpcompat.scan_results"); + $scan_results = get_option( 'wpephpcompat.scan_results' ); - foreach ($directories as $directory) { - $this->debugLog("Processing: " . $directory->post_title); + foreach ( $directories as $directory ) { + $this->debugLog( 'Processing: ' . $directory->post_title ); - $report = $this->processFile($directory->post_content); + $report = $this->processFile( $directory->post_content ); if ( ! $report ) { - $report = "PHP " . $this->test_version . " compatible."; + $report = 'PHP ' . $this->test_version . ' compatible.'; } - $scan_results .= "Name: " . $directory->post_title . "\n\n" . $report . "\n"; + $scan_results .= 'Name: ' . $directory->post_title . "\n\n" . $report . "\n"; - $update = get_post_meta( $directory->ID, "update", true ); + $update = get_post_meta( $directory->ID, 'update', true ); if ( ! empty( $update ) ) { - $version = get_post_meta( $directory->ID, "version", true ); - $scan_results .= "Update Available: " . $update . "; Current Version: " . $version . ";\n"; + $version = get_post_meta( $directory->ID, 'version', true ); + $scan_results .= 'Update Available: ' . $update . '; Current Version: ' . $version . ";\n"; } $scan_results .= "\n"; - update_option( "wpephpcompat.scan_results", $scan_results ); + update_option( 'wpephpcompat.scan_results', $scan_results ); - wp_delete_post($directory->ID); + wp_delete_post( $directory->ID ); } - update_option( "wpephpcompat.status", "0" ); + update_option( 'wpephpcompat.status', '0' ); - $this->debugLog( "Scan finished." ); + $this->debugLog( 'Scan finished.' ); return; } @@ -191,12 +194,12 @@ public function start_test() { * @return string Scan results. */ public function processFile( $dir ) { - $this->values['files'] = $dir; + $this->values['files'] = $dir; //$this->values['ignored'] = $this->generateIgnoreList(); $this->values['testVersion'] = $this->test_version; - $this->values['standard'] = "PHPCompatibility"; - $this->values['reportWidth'] = "9999"; - $this->values['extensions'] = array("php"); + $this->values['standard'] = 'PHPCompatibility'; + $this->values['reportWidth'] = '9999'; + $this->values['extensions'] = array( 'php' ); PHP_CodeSniffer::setConfigData( 'testVersion', $this->test_version, true ); @@ -215,14 +218,14 @@ public function processFile( $dir ) { * @return null */ public function generateDirectoryList() { - if ( !function_exists( 'get_plugins' ) ) { + if ( ! function_exists( 'get_plugins' ) ) { /** * Summary. */ require_once ABSPATH . 'wp-admin/includes/plugin.php'; } - $plugin_base = dirname($this->base) . DIRECTORY_SEPARATOR; + $plugin_base = dirname( $this->base ) . DIRECTORY_SEPARATOR; $all_plugins = get_plugins(); @@ -230,33 +233,33 @@ public function generateDirectoryList() { foreach ( $all_plugins as $k => $v ) { //Exclude our plugin. - if ($v["Name"] === "WP Engine PHP Compatibility") { + if ( $v['Name'] === 'WP Engine PHP Compatibility' ) { continue; } - //Exclude active plugins if only_active = "yes". - if ($this->only_active === "yes") { - //Get array of active plugins. - $active_plugins = get_option('active_plugins'); + // Exclude active plugins if only_active = "yes". + if ( $this->only_active === 'yes' ) { + // Get array of active plugins. + $active_plugins = get_option( 'active_plugins' ); if ( ! in_array( $k, $active_plugins ) ) { continue; } } - $plugin_path = $plugin_base . plugin_dir_path($k); + $plugin_path = $plugin_base . plugin_dir_path( $k ); - $id = $this->addDirectory($v["Name"], $plugin_path); + $id = $this->addDirectory( $v['Name'], $plugin_path ); //Check for plugin updates. foreach ( $update_plugins->response as $uk => $uv ) { //If we have a match. if ( $uk === $k ) { - $this->debugLog( "An update exists for: " . $v["Name"] ); + $this->debugLog( 'An update exists for: ' . $v['Name'] ); //Save the update version. - update_post_meta( $id, "update", $uv->new_version ); + update_post_meta( $id, 'update', $uv->new_version ); //Save the current version. - update_post_meta( $id, "version", $v["Version"] ); + update_post_meta( $id, 'version', $v['Version'] ); } } } @@ -265,7 +268,7 @@ public function generateDirectoryList() { $all_themes = wp_get_themes(); foreach ( $all_themes as $k => $v ) { - if ( $this->only_active === "yes" ) { + if ( $this->only_active === 'yes' ) { $current_theme = wp_get_theme(); if ($all_themes[$k]->Name != $current_theme->Name) continue; @@ -273,7 +276,7 @@ public function generateDirectoryList() { $theme_path = $all_themes[$k]->theme_root . DIRECTORY_SEPARATOR . $k . DIRECTORY_SEPARATOR; - $this->addDirectory($all_themes[$k]->Name, $theme_path); + $this->addDirectory( $all_themes[$k]->Name, $theme_path ); } } @@ -300,22 +303,26 @@ private function cleanReport( $report ) { * @return THING */ public function cleanAfterScan() { - //Delete options created during the scan. - delete_option( "wpephpcompat.lock" ); - delete_option( "wpephpcompat.status" ); - delete_option( "wpephpcompat.scan_results" ); - delete_option( "wpephpcompat.test_version" ); - delete_option( "wpephpcompat.only_active" ); - delete_option( "wpephpcompat.numdirs" ); - //Clear scheduled cron. - wp_clear_scheduled_hook( "wpephpcompat_start_test_cron" ); + // Delete options created during the scan. + delete_option( 'wpephpcompat.lock' ); + delete_option( 'wpephpcompat.status' ); + delete_option( 'wpephpcompat.scan_results' ); + delete_option( 'wpephpcompat.test_version' ); + delete_option( 'wpephpcompat.only_active' ); + delete_option( 'wpephpcompat.numdirs' ); + + // Clear scheduled cron. + wp_clear_scheduled_hook( 'wpephpcompat_start_test_cron' ); //Make sure all directories are removed from the queue. - $args = array( 'posts_per_page' => -1, 'post_type' => 'wpephpcompat_jobs' ); + $args = array( + 'posts_per_page' => -1, + 'post_type' => 'wpephpcompat_jobs' + ); $directories = get_posts( $args ); - foreach ($directories as $directory) { - wp_delete_post($directory->ID); + foreach ( $directories as $directory ) { + wp_delete_post( $directory->ID ); } } @@ -350,7 +357,7 @@ private function debugLog( $message ){ error_log( print_r( $message , true ) ); } else { - error_log( "WPE PHP Compatibility: " . $message ); + error_log( 'WPE PHP Compatibility: ' . $message ); } } } From 139a5c85a5b697d70c7740a0c1b23d0e4717e8a5 Mon Sep 17 00:00:00 2001 From: Steven K Word Date: Mon, 20 Jun 2016 14:05:53 -0700 Subject: [PATCH 082/557] PHPDoc --- index.php | 35 +++++++++++++++++++++++++---------- src/wpephpcompat.php | 17 +++++++++++------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/index.php b/index.php index 08dd755..f54b332 100644 --- a/index.php +++ b/index.php @@ -10,7 +10,7 @@ require_once( __DIR__ . '/vendor/autoload.php' ); -//Build our tools page. +// Build our tools page. add_action( 'admin_menu', 'wpephpcompat_create_menu' ); // Load our JavaScript. @@ -24,13 +24,16 @@ // Create custom post type. add_action( 'init', 'wpephpcompat_create_job_queue' ); -//Add the phpcompat WP-CLI command. +// Add the phpcompat WP-CLI command. if ( defined( 'WP_CLI' ) && WP_CLI ) { require_once( __DIR__ . '/src/wpcli.php' ); } /** - * [wpephpcompat_start_test description] + * Summary. + * + * Description. + * * @since 1.0.0 * @return [type] [description] */ @@ -40,8 +43,8 @@ function wpephpcompat_start_test() { $wpephpc = new \WPEPHPCompat( __DIR__ ); if ( isset( $_POST['startScan'] ) ) { - $test_version = $_POST['test_version']; - $only_active = $_POST['only_active']; + $test_version = sanitize_text_field( $_POST['test_version'] ); + $only_active = sanitize_text_field( $_POST['only_active'] ); $wpephpc->test_version = $test_version; @@ -54,9 +57,14 @@ function wpephpcompat_start_test() { wp_die(); } -//TODO: Use heartbeat API. + /** - * [wpephpcompat_check_status description] + * Summary. + * + * Description. + * + * @todo Use heartbeat API. + * * @since 1.0.0 * @return [type] [description] */ @@ -110,6 +118,7 @@ function wpephpcompat_create_job_queue() { /** * Enqueue our JavaScript and CSS. + * * @since 1.0.0 * @return null */ @@ -131,7 +140,10 @@ function wpephpcompat_enqueue() { } /** - * [wpephpcompat_create_menu description] + * Summary. + * + * Description. + * * @since 1.0.0 * @return [type] [description] */ @@ -141,7 +153,10 @@ function wpephpcompat_create_menu() { } /** - * [wpephpcompat_settings_page description] + * Summary. + * + * Description. + * * @since 1.0.0 * @return [type] [description] */ @@ -162,7 +177,7 @@ function wpephpcompat_settings_page() {
-
+

diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 30916b7..626c6fb 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -78,6 +78,8 @@ class WPEPHPCompat { /** * Summary. * + * Description. + * * @param [type] $dir [description] */ function __construct( $dir ) { @@ -190,6 +192,7 @@ public function start_test() { /** * Runs the actual PHPCompatibility test. + * * @since 1.0.0 * @return string Scan results. */ @@ -214,6 +217,7 @@ public function processFile( $dir ) { /** * Generate a list of directories to scan and populate the queue. + * * @since 1.0.0 * @return null */ @@ -222,7 +226,7 @@ public function generateDirectoryList() { /** * Summary. */ - require_once ABSPATH . 'wp-admin/includes/plugin.php'; + require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); } $plugin_base = dirname( $this->base ) . DIRECTORY_SEPARATOR; @@ -287,10 +291,10 @@ public function generateDirectoryList() { * @return string The cleaned report. */ private function cleanReport( $report ) { - //Remove unnecessary overview. + // Remove unnecessary overview. $report = preg_replace ( '/Time:.+\n/si', '', $report ); - //Remove whitespace. + // Remove whitespace. $report = trim( $report ); return $report; @@ -300,7 +304,7 @@ private function cleanReport( $report ) { * Remove all database entries created by the scan. * * @since 1.0.0 - * @return THING + * @return null */ public function cleanAfterScan() { // Delete options created during the scan. @@ -331,7 +335,7 @@ public function cleanAfterScan() { * * @param string $name Plugin or theme name. * @param string $path Full path to the plugin or theme directory. - * @return THING + * @return null */ private function addDirectory( $name, $path ) { $dir = array( @@ -339,7 +343,7 @@ private function addDirectory( $name, $path ) { 'post_content' => $path, 'post_status' => 'publish', 'post_author' => 1, - 'post_type' => 'wpephpcompat_jobs' + 'post_type' => 'wpephpcompat_jobs' ); return wp_insert_post( $dir ); @@ -350,6 +354,7 @@ private function addDirectory( $name, $path ) { * * @since 1.0.0 * @param string $message Message to log. + * @return null */ private function debugLog( $message ){ if ( WP_DEBUG === true ) { From 61212289b770f1f563663c4a585a656b53d9c5ee Mon Sep 17 00:00:00 2001 From: Steven K Word Date: Mon, 20 Jun 2016 14:11:47 -0700 Subject: [PATCH 083/557] Un-camelCase --- index.php | 4 ++-- src/wpcli.php | 2 +- src/wpephpcompat.php | 44 ++++++++++++++++++++++---------------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/index.php b/index.php index f54b332..042e68d 100644 --- a/index.php +++ b/index.php @@ -50,7 +50,7 @@ function wpephpcompat_start_test() { $wpephpc->only_active = $only_active; - $wpephpc->cleanAfterScan(); + $wpephpc->clean_after_scan(); } echo esc_html( $wpephpc->start_test() ); @@ -89,7 +89,7 @@ function wpephpcompat_check_status() { $to_encode['results'] = esc_html( $scan_results ); $wpephpc = new \WPEPHPCompat( __DIR__ ); - $wpephpc->cleanAfterScan(); + $wpephpc->clean_after_scan(); } echo json_encode( $to_encode ); diff --git a/src/wpcli.php b/src/wpcli.php index be9d7d7..11cb3ae 100644 --- a/src/wpcli.php +++ b/src/wpcli.php @@ -45,7 +45,7 @@ function __invoke( $args, $assoc_args ) { $wpephpc = new \WPEPHPCompat( $root_dir ); - $wpephpc->cleanAfterScan(); + $wpephpc->clean_after_scan(); $wpephpc->test_version = $test_version; diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 626c6fb..cc2b6b8 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -96,18 +96,18 @@ function __construct( $dir ) { */ public function start_test() { - $this->debugLog( 'startScan: ' . isset( $_POST['startScan'] ) ); + $this->debug_log( 'startScan: ' . isset( $_POST['startScan'] ) ); // Try to lock. $lock_result = add_option( 'wpephpcompat.lock', time(), '', 'no' ); - $this->debugLog( 'lock: ' . $lock_result ); + $this->debug_log( 'lock: ' . $lock_result ); if ( ! $lock_result ) { $lock_result = get_option( 'wpephpcompat.lock' ); // Bail if we were unable to create a lock, or if the existing lock is still valid. if ( ! $lock_result || ( $lock_result > ( time() - MINUTE_IN_SECONDS ) ) ) { - $this->debugLog( 'Process already running (locked), returning.' ); + $this->debug_log( 'Process already running (locked), returning.' ); $timestamp = wp_next_scheduled( 'wpephpcompat_start_test_cron' ); @@ -121,11 +121,11 @@ public function start_test() { // Check to see if scan has already started. $scan_status = get_option( 'wpephpcompat.status' ); - $this->debugLog( 'scan status: ' . $scan_status ); + $this->debug_log( 'scan status: ' . $scan_status ); if ( ! $scan_status ) { - $this->debugLog( 'Generating directory list.' ); + $this->debug_log( 'Generating directory list.' ); //Add plugins and themes. - $this->generateDirectoryList(); + $this->generate_directory_list(); add_option( 'wpephpcompat.status', '1' ); add_option( 'wpephpcompat.test_version', $this->test_version ); @@ -144,12 +144,12 @@ public function start_test() { 'post_type' => 'wpephpcompat_jobs' ); $directories = get_posts( $args ); - $this->debugLog( count( $directories ) . ' plugins left to process.' ); + $this->debug_log( count( $directories ) . ' plugins left to process.' ); // If there are no directories to scan, we're finished! if ( ! $directories ) { - $this->debugLog( 'No more plugins to process.' ); - $this->cleanAfterScan(); + $this->debug_log( 'No more plugins to process.' ); + $this->clean_after_scan(); return; } @@ -159,9 +159,9 @@ public function start_test() { $scan_results = get_option( 'wpephpcompat.scan_results' ); foreach ( $directories as $directory ) { - $this->debugLog( 'Processing: ' . $directory->post_title ); + $this->debug_log( 'Processing: ' . $directory->post_title ); - $report = $this->processFile( $directory->post_content ); + $report = $this->process_file( $directory->post_content ); if ( ! $report ) { $report = 'PHP ' . $this->test_version . ' compatible.'; @@ -185,7 +185,7 @@ public function start_test() { update_option( 'wpephpcompat.status', '0' ); - $this->debugLog( 'Scan finished.' ); + $this->debug_log( 'Scan finished.' ); return; } @@ -196,7 +196,7 @@ public function start_test() { * @since 1.0.0 * @return string Scan results. */ - public function processFile( $dir ) { + public function process_file( $dir ) { $this->values['files'] = $dir; //$this->values['ignored'] = $this->generateIgnoreList(); $this->values['testVersion'] = $this->test_version; @@ -212,7 +212,7 @@ public function processFile( $dir ) { $report = ob_get_clean(); - return $this->cleanReport( $report ); + return $this->clean_report( $report ); } /** @@ -221,7 +221,7 @@ public function processFile( $dir ) { * @since 1.0.0 * @return null */ - public function generateDirectoryList() { + public function generate_directory_list() { if ( ! function_exists( 'get_plugins' ) ) { /** * Summary. @@ -253,13 +253,13 @@ public function generateDirectoryList() { $plugin_path = $plugin_base . plugin_dir_path( $k ); - $id = $this->addDirectory( $v['Name'], $plugin_path ); + $id = $this->add_directory( $v['Name'], $plugin_path ); //Check for plugin updates. foreach ( $update_plugins->response as $uk => $uv ) { //If we have a match. if ( $uk === $k ) { - $this->debugLog( 'An update exists for: ' . $v['Name'] ); + $this->debug_log( 'An update exists for: ' . $v['Name'] ); //Save the update version. update_post_meta( $id, 'update', $uv->new_version ); //Save the current version. @@ -280,7 +280,7 @@ public function generateDirectoryList() { $theme_path = $all_themes[$k]->theme_root . DIRECTORY_SEPARATOR . $k . DIRECTORY_SEPARATOR; - $this->addDirectory( $all_themes[$k]->Name, $theme_path ); + $this->add_directory( $all_themes[$k]->Name, $theme_path ); } } @@ -290,7 +290,7 @@ public function generateDirectoryList() { * @param string $report The full report. * @return string The cleaned report. */ - private function cleanReport( $report ) { + private function clean_report( $report ) { // Remove unnecessary overview. $report = preg_replace ( '/Time:.+\n/si', '', $report ); @@ -306,7 +306,7 @@ private function cleanReport( $report ) { * @since 1.0.0 * @return null */ - public function cleanAfterScan() { + public function clean_after_scan() { // Delete options created during the scan. delete_option( 'wpephpcompat.lock' ); delete_option( 'wpephpcompat.status' ); @@ -337,7 +337,7 @@ public function cleanAfterScan() { * @param string $path Full path to the plugin or theme directory. * @return null */ - private function addDirectory( $name, $path ) { + private function add_directory( $name, $path ) { $dir = array( 'post_title' => $name, 'post_content' => $path, @@ -356,7 +356,7 @@ private function addDirectory( $name, $path ) { * @param string $message Message to log. * @return null */ - private function debugLog( $message ){ + private function debug_log( $message ){ if ( WP_DEBUG === true ) { if ( is_array( $message ) || is_object( $message ) ) { error_log( print_r( $message , true ) ); From 7e82efa62508f9df9cf74292b20dfb8a5bb5db46 Mon Sep 17 00:00:00 2001 From: Steven K Word Date: Mon, 20 Jun 2016 15:24:48 -0700 Subject: [PATCH 084/557] OOPify the plugin loader --- index.php | 418 +++++++++++++++++++++++-------------------- src/wpcli.php | 6 +- src/wpephpcompat.php | 8 +- 3 files changed, 230 insertions(+), 202 deletions(-) diff --git a/index.php b/index.php index 042e68d..7f33a6b 100644 --- a/index.php +++ b/index.php @@ -8,21 +8,10 @@ Author URI: http://wpengine.com */ -require_once( __DIR__ . '/vendor/autoload.php' ); - -// Build our tools page. -add_action( 'admin_menu', 'wpephpcompat_create_menu' ); - -// Load our JavaScript. -add_action( 'admin_enqueue_scripts', 'wpephpcompat_enqueue' ); +// Exit if this file is directly accessed +if ( ! defined( 'ABSPATH' ) ) exit; -// The action to run the compatibility test. -add_action( 'wp_ajax_wpephpcompat_start_test', 'wpephpcompat_start_test' ); -add_action( 'wp_ajax_wpephpcompat_check_status', 'wpephpcompat_check_status' ); -add_action( 'wpephpcompat_start_test_cron', 'wpephpcompat_start_test' ); - -// Create custom post type. -add_action( 'init', 'wpephpcompat_create_job_queue' ); +require_once( __DIR__ . '/vendor/autoload.php' ); // Add the phpcompat WP-CLI command. if ( defined( 'WP_CLI' ) && WP_CLI ) { @@ -31,206 +20,249 @@ /** * Summary. - * - * Description. - * - * @since 1.0.0 - * @return [type] [description] */ -function wpephpcompat_start_test() { - global $wpdb; +class WPEngine_PHPCompat { + + /* Define and register singleton */ + private static $instance = false; + + /** + * Summary. + * + * @return [type] [description] + */ + public static function instance() { + if( ! self::$instance ) { + self::$instance = new self; + self::$instance->init(); + } + return self::$instance; + } - $wpephpc = new \WPEPHPCompat( __DIR__ ); + /** + * Initialize hooks and setup environment variables. + * + * @since 0.1.0 + */ + public static function init() { - if ( isset( $_POST['startScan'] ) ) { - $test_version = sanitize_text_field( $_POST['test_version'] ); - $only_active = sanitize_text_field( $_POST['only_active'] ); + // Build our tools page. + add_action( 'admin_menu', array( self::instance(), 'create_menu' ) ); - $wpephpc->test_version = $test_version; + // Load our JavaScript. + add_action( 'admin_enqueue_scripts', array( self::instance(), 'admin_enqueue' ) ); - $wpephpc->only_active = $only_active; + // The action to run the compatibility test. + add_action( 'wp_ajax_wpephpcompat_start_test', array( self::instance(), 'start_test' ) ); + add_action( 'wp_ajax_wpephpcompat_check_status', array( self::instance(), 'check_status' ) ); + add_action( 'wpephpcompat_start_test_cron', array( self::instance(), 'start_test' ) ); - $wpephpc->clean_after_scan(); + // Create custom post type. + add_action( 'init', array( self::instance(), 'create_job_queue' ) ); } - echo esc_html( $wpephpc->start_test() ); - wp_die(); -} - - -/** - * Summary. - * - * Description. - * - * @todo Use heartbeat API. - * - * @since 1.0.0 - * @return [type] [description] - */ -function wpephpcompat_check_status() { - $scan_status = get_option( 'wpephpcompat.status' ); - $count_jobs = wp_count_posts( 'wpephpcompat_jobs' ); - $total_jobs = get_option( 'wpephpcompat.numdirs' ); - - $to_encode = array( - 'status' => $scan_status, - 'count' => $count_jobs->publish, - 'total' => $total_jobs, - 'progress' => 100 - ( ( $count_jobs->publish / $total_jobs ) * 100 ) - ); - - // If the scan is still running. - if ( $scan_status ) { - $to_encode['results'] = '0'; - } else { - // Else return the results and clean up! - $scan_results = get_option( 'wpephpcompat.scan_results' ); - $to_encode['results'] = esc_html( $scan_results ); + /** + * Summary. + * + * Description. + * + * @since 1.0.0 + * @action wp_ajax_wpephpcompat_start_test + * @action wpephpcompat_start_test_cron + * @return [type] [description] + */ + function start_test() { + global $wpdb; $wpephpc = new \WPEPHPCompat( __DIR__ ); - $wpephpc->clean_after_scan(); - } - - echo json_encode( $to_encode ); - wp_die(); -} + if ( isset( $_POST['startScan'] ) ) { + $test_version = sanitize_text_field( $_POST['test_version'] ); + $only_active = sanitize_text_field( $_POST['only_active'] ); -/** - * Create custom post type to store the directories we need to process. - * - * @since 1.0.0 - * @return null - */ -function wpephpcompat_create_job_queue() { - register_post_type( 'wpephpcompat_jobs', - array( - 'labels' => array( - 'name' => __( 'Jobs' ), - 'singular_name' => __( 'Job' ) - ), - 'public' => false, - 'has_archive' => false, - ) - ); -} + $wpephpc->test_version = $test_version; + $wpephpc->only_active = $only_active; + $wpephpc->clean_after_scan(); + } -/** - * Enqueue our JavaScript and CSS. - * - * @since 1.0.0 - * @return null - */ -function wpephpcompat_enqueue() { - wp_enqueue_style( 'wpephpcompat-style', plugins_url( '/src/css/style.css', __FILE__ ) ); - - wp_enqueue_script( 'wpephpcompat-handlebars', plugins_url( '/src/js/handlebars.js', __FILE__ ), array( 'jquery' ) ); - - wp_enqueue_script( 'wpephpcompat-download', plugins_url( '/src/js/download.min.js', __FILE__ ) ); + echo esc_html( $wpephpc->start_test() ); + wp_die(); + } - wp_enqueue_script( 'wpephpcompat', plugins_url( '/src/js/run.js', __FILE__ ), array('jquery', 'wpephpcompat-handlebars', 'wpephpcompat-download') ); + /** + * Check the progress or result of the tests. + * + * @todo Use heartbeat API. + * @since 1.0.0 + * @action wp_ajax_wpephpcompat_check_status + * @return null + */ + function check_status() { + $scan_status = get_option( 'wpephpcompat.status' ); + $count_jobs = wp_count_posts( 'wpephpcompat_jobs' ); + $total_jobs = get_option( 'wpephpcompat.numdirs' ); + + $to_encode = array( + 'status' => $scan_status, + 'count' => $count_jobs->publish, + 'total' => $total_jobs, + 'progress' => 100 - ( ( $count_jobs->publish / $total_jobs ) * 100 ) + ); + + // If the scan is still running. + if ( $scan_status ) { + $to_encode['results'] = '0'; + } else { + // Else return the results and clean up! + $scan_results = get_option( 'wpephpcompat.scan_results' ); + $to_encode['results'] = esc_html( $scan_results ); + + $wpephpc = new \WPEPHPCompat( __DIR__ ); + $wpephpc->clean_after_scan(); + } + + echo json_encode( $to_encode ); + wp_die(); - // Progress Bar - wp_enqueue_script( 'jquery-ui-progressbar' ); - wp_enqueue_style( 'jquery-style', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css' ); + } + /** + * Create custom post type to store the directories we need to process. + * + * @since 1.0.0 + * @return null + */ + function create_job_queue() { + register_post_type( 'wpephpcompat_jobs', + array( + 'labels' => array( + 'name' => __( 'Jobs' ), + 'singular_name' => __( 'Job' ) + ), + 'public' => false, + 'has_archive' => false, + ) + ); + } - wp_localize_script( 'wpephpcompat', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' )) ); -} + /** + * Enqueue our JavaScript and CSS. + * + * @since 1.0.0 + * @action admin_enqueue_scripts + * @return null + */ + function admin_enqueue() { + // Styles + wp_enqueue_style( 'wpephpcompat-style', plugins_url( '/src/css/style.css', __FILE__ ) ); + + // Scripts + wp_enqueue_script( 'wpephpcompat-handlebars', plugins_url( '/src/js/handlebars.js', __FILE__ ), array( 'jquery' ) ); + wp_enqueue_script( 'wpephpcompat-download', plugins_url( '/src/js/download.min.js', __FILE__ ) ); + wp_enqueue_script( 'wpephpcompat', plugins_url( '/src/js/run.js', __FILE__ ), array('jquery', 'wpephpcompat-handlebars', 'wpephpcompat-download') ); + + // Progress Bar + wp_enqueue_script( 'jquery-ui-progressbar' ); + wp_enqueue_style( 'jquery-style', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/smoothness/jquery-ui.css' ); + + // PHP to JS vars + wp_localize_script( 'wpephpcompat', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' )) ); + } -/** - * Summary. - * - * Description. - * - * @since 1.0.0 - * @return [type] [description] - */ -function wpephpcompat_create_menu() { - //Create Tools sub-menu. - $wpeallowheartbeat_settings_page = add_submenu_page( 'tools.php', 'PHP Compatibility', 'PHP Compatibility', 'administrator', __FILE__, 'wpephpcompat_settings_page' ); -} + /** + * Add the settings page to the wp-admin menu. + * + * @since 1.0.0 + * @action admin_menu + * @return null + */ + function create_menu() { + // Create Tools sub-menu. + $wpeallowheartbeat_settings_page = add_submenu_page( 'tools.php', 'PHP Compatibility', 'PHP Compatibility', 'administrator', __FILE__, array( self::instance(), 'settings_page' ) ); + } -/** - * Summary. - * - * Description. - * - * @since 1.0.0 - * @return [type] [description] - */ -function wpephpcompat_settings_page() { - ?> -
-
-

WP Engine PHP Compatibility

-
-
- Developer mode -
-

-

Scan Options

- - - - - - - - - - - -
-
-
-
- -

- -
-

-

-
{{#if updateAvailable}}{{/if}}{{#unless passed}}
{{warnings}} Warnings
{{errors}} Errors
{{/unless}}
+
{{#if updateAvailable}}{{/if}}{{#if warnings}}
{{warnings}} Warnings
{{/if}}{{#if errors}}
{{errors}} Errors
{{/if}}
Date: Thu, 7 Jul 2016 17:33:44 -0500 Subject: [PATCH 174/557] Add Gruntfile to build readme.md. --- .gitignore | 1 + Gruntfile.js | 21 +++++++++ package.json | 7 +++ readme.md | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 Gruntfile.js create mode 100644 package.json create mode 100644 readme.md diff --git a/.gitignore b/.gitignore index 48b8bf9..140fd58 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ vendor/ +node_modules/ diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..8638fc8 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,21 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + wp_readme_to_markdown: { + options: { + screenshot_url: 'assets/{screenshot}.png' + }, + your_target: { + files: { + 'readme.md': 'readme.txt' + }, + }, + }, + }); + + grunt.loadNpmTasks('grunt-wp-readme-to-markdown'); + + grunt.registerTask('default', [ + 'wp_readme_to_markdown' + ]); +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..f8622f9 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "php-compatibility-checker", + "devDependencies": { + "grunt": "^1.0.1", + "grunt-wp-readme-to-markdown": "^2.0.0" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..87da5c0 --- /dev/null +++ b/readme.md @@ -0,0 +1,131 @@ +# PHP Compatibility Checker # +**Contributors:** [wpengine](https://profiles.wordpress.org/wpengine), [octalmage](https://profiles.wordpress.org/octalmage), [stevenkword](https://profiles.wordpress.org/stevenkword), [taylor4484](https://profiles.wordpress.org/taylor4484) +**Tags:** php 7, php 5.5, php, version, compatibility, checker, wp engine, wpe, wpengine +**Requires at least:** 3.0.1 +**Tested up to:** 4.5 +**Stable tag:** 1.0.2 +**License:** GPLv2 or later +**License URI:** http://www.gnu.org/licenses/gpl-2.0.html + +Make sure your plugins and themes are compatible with newer PHP versions. + +## Description ## + +The WP Engine PHP Compatibility Checker can be used by any WordPress website on any web host to check PHP version compatibility. + +This plugin will lint theme and plugin code inside your WordPress file system and give you back a report of compatibility issues for you to fix. Compatibility issues are categorized into errors and warnings and will list the file and line number of the offending code, as well as the info about why that line of code is incompatible with the chosen version of PHP. The plugin will also suggest updates to themes and plugins, as a new version may offer compatible code. + +**This plugin does not execute your theme and plugin code, as such this plugin cannot detect runtime compatibility issues.** + +**Please note that linting code is not perfect. We are aware of a few infrequent false positives, we are continuously working to ensure the ** + +### Update to PHP 7 ### +* Use this plugin to check your site for compatibility for PHP 7! +* As of July 2016, 59.3% of WordPress websites run a PHP version less PHP 5.5. +* These versions of PHP have been deprecated and unsupported for over 9 months. +* Only 1.8% of WordPress websites run PHP 7, the current main version of PHP. + + +### Disclaimer ### +*While this plugin is written to detect as many problems as accurately as possible, 100% reliable detection is very difficult to ensure. It is best practice to run comprehensive tests before you migrate to a new PHP version.* + +The plugin was created by WP Engine to help the WordPress community increase adoption of modern PHP versions. We [welcome contributors](https://github.com/wpengine/phpcompat) to this plugin, and are excited to see how developers and other WordPress hosts use this plugin. + +To disclose security issues for this plugin please email WordPress@wpengine.com + +## Installation ## + +*Note: If you have WordPress 2.7 or above you can simply go to 'Plugins' > 'Add New' in the WordPress admin and search for "Indeed Apply Shortcode" and install it from there.* + +1. Upload `phpcompat` to the `/wpengine-wp-content/plugins/` directory +2. Activate the plugin through the 'Plugins' menu in WordPress + +You will find the plugin options in the WP Admin ‘Tools => PHP Compatibility’ menu. Once you click ‘run’ it will take a few minutes to conduct the test. While the test is running, you cannot navigate away from the page. + +There are WP-CLI commands available see the [Other Notes](https://wordpress.org/plugins/php-compatibility-checker/other_notes/) tab for details. + +## Other Notes ## + +PHP Compatibility Checker includes WP-CLI command support: + +`wp phpcompat [--scan=]` + +` + + PHP version to test. + +[--scan=] + Whether to scan only active plugins and themes or all of them. + default: active + options: + - active + - all +` +Example: `wp phpcompat 5.5 --scan=active` + + +## Frequently Asked Questions ## + +1) Will this work outside of the WP Engine hosting account? + +Yes, this plugin can be used any ANY WordPress website on ANY host. + +2) Are there WP-CLI commands available? +Yes, this plugin does extend WP-CLI and provide commands. See the [Other Notes](https://wordpress.org/plugins/php-compatibility-checker/other_notes/) tab for details. + +3) Can I use this to test non-WordPress PHP Projects? + +Yes! While you cannot use this WordPress plugin to test your non-WordPress projects, you can use the [Open Source PHPCompatibility Library](https://github.com/wimg/PHPCompatibility) that this plugin is built on. + +4) Why was my plugin/theme skipped? +Some servers have timeouts to prevent long running queries, this is commonly 60 seconds. This can prevent the checker from being able to process large themes or plugins. You should check with your host to see if this timeout can be temporarily removed. The best way around this timeout issues is to run this plugin on a [local copy](https://make.wordpress.org/core/handbook/tutorials/installing-a-local-server/) of your site. + +5) I found a bug, or have a suggestion, can I contribute back? + +Yes! WP Engine has a public GitHub repo where you can contribute back to this plugin. Please open an issue on the [Plugin GitHub](https://github.com/wpengine/phpcompat). We actively develop this plugin, and are always happy to receive pull requests. + +The plugin was created by WP Engine to help the WordPress community increase adoption of modern PHP versions. We welcome contributors to this plugin, and are excited to see how developers and other WordPress hosts use this plugin. + +To disclose security issues for this plugin please email WordPress@wpengine.com + +## Screenshots ## + +### 1. Main screen: compatibility checker options ### +![Main screen: compatibility checker options](assets/screenshot-1.png) + +### 2. Compatibility results screen ### +![Compatibility results screen](assets/screenshot-2.png) + + +## Changelog ## + +### 1.0.2 ### +- Added additional role protections +- Changed the UI colors to better understand output at a glance +- Exclude checking node_modules and tmp directories +- Fixed issue where a child theme's parent wasn't correctly checked + +### 1.0.1 ### +- Updated compatibility library with a few bugfixes +- Added skip logic to prevent checker from hanging + +### 1.0.0 ### +- Major update to add PHP 7 checking support +- Improved the UX of the progress bar +- Fixed bug with the way the plugin menu was registered + +### 0.1.0 ### +- Initial version +- PHP 5.5, 5.4, and 5.3 Support +- Basic WP-CLI Commands + +## Upgrade Notice ## + +### 1.0.2 ### +- Added additional role protections +- Changed the UI colors to better understand output at a glance +- Exclude checking node_modules and tmp directories +- Fixed issue where a child theme's parent wasn't correctly checked + +Did you find this plugin useful? Leave us a review: +https://wordpress.org/support/view/plugin-reviews/php-compatibility-checker \ No newline at end of file From efa88030a361b2b1c4303299e4f7c956290afa8d Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Thu, 7 Jul 2016 17:34:22 -0500 Subject: [PATCH 175/557] Add newline to end of Gruntfile.js --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 8638fc8..66dbb5d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -18,4 +18,4 @@ module.exports = function(grunt) { grunt.registerTask('default', [ 'wp_readme_to_markdown' ]); -}; \ No newline at end of file +}; From a0fb2f58e51c10077d4961091648a6b5fedaa359 Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Thu, 7 Jul 2016 18:18:42 -0500 Subject: [PATCH 176/557] Remove review line. --- readme.md | 3 --- readme.txt | 3 --- 2 files changed, 6 deletions(-) diff --git a/readme.md b/readme.md index 87da5c0..ad74b83 100644 --- a/readme.md +++ b/readme.md @@ -126,6 +126,3 @@ To disclose security issues for this plugin please email WordPress@wpengine.com - Changed the UI colors to better understand output at a glance - Exclude checking node_modules and tmp directories - Fixed issue where a child theme's parent wasn't correctly checked - -Did you find this plugin useful? Leave us a review: -https://wordpress.org/support/view/plugin-reviews/php-compatibility-checker \ No newline at end of file diff --git a/readme.txt b/readme.txt index 71157b6..d930114 100644 --- a/readme.txt +++ b/readme.txt @@ -122,6 +122,3 @@ To disclose security issues for this plugin please email WordPress@wpengine.com - Changed the UI colors to better understand output at a glance - Exclude checking node_modules and tmp directories - Fixed issue where a child theme's parent wasn't correctly checked - -Did you find this plugin useful? Leave us a review: -https://wordpress.org/support/view/plugin-reviews/php-compatibility-checker \ No newline at end of file From 45b21d95c51da6bdc429afb37bcca674d70e1219 Mon Sep 17 00:00:00 2001 From: Simon Prosser Date: Fri, 8 Jul 2016 00:25:17 +0100 Subject: [PATCH 177/557] fix 'failed to delete buffer. No buffer to delete' --- src/wpephpcompat.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wpephpcompat.php b/src/wpephpcompat.php index 1d856a1..216f318 100644 --- a/src/wpephpcompat.php +++ b/src/wpephpcompat.php @@ -410,7 +410,7 @@ private function is_command_line() */ private function close_connection( $body ) { ignore_user_abort( true ); - ob_end_clean(); + if (ob_get_length()) ob_end_clean(); // Start buffering. ob_start(); // Echo our response. From 1d813a8412f38c691e8dbda1d7b1d4cd22183fdd Mon Sep 17 00:00:00 2001 From: Jason Stallings Date: Thu, 7 Jul 2016 18:30:53 -0500 Subject: [PATCH 178/557] Update screenshots. --- assets/screenshot-1.png | Bin 142622 -> 168599 bytes assets/screenshot-2.png | Bin 251551 -> 230234 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/screenshot-1.png b/assets/screenshot-1.png index ee865068a15c8e85296e99b32aa884eaa5cd2d4c..6b704fda7ac852443039266226f2848d74a21069 100644 GIT binary patch literal 168599 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

-Ji4|dA9&!_iEsGrFM)^-L>%Ng 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