From d94b54d8bc9aff1efa8d7d646f5ed492c8a3db36 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Mon, 19 Sep 2016 02:09:04 +0530 Subject: [PATCH 001/159] Add support for tax_query NOT IN operator --- classes/class-ep-api.php | 42 ++++++++++++++++++++++++-------- tests/test-single-site.php | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 27bf8c7431..bd4f4bdc98 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -963,6 +963,10 @@ public function format_args( $args ) { if ( ! empty( $args['tax_query'] ) ) { $tax_filter = array(); + $tax_must_not_filter = array(); + + // Main tax_query array for ES + $es_tax_query = array(); foreach( $args['tax_query'] as $single_tax_query ) { if ( ! empty( $single_tax_query['terms'] ) ) { @@ -978,14 +982,24 @@ public function format_args( $args ) { $terms_obj = array( 'terms.' . $single_tax_query['taxonomy'] . '.' . $field => $terms, ); - - // Use the AND operator if passed - if ( ! empty( $single_tax_query['operator'] ) && 'AND' === $single_tax_query['operator'] ) { - $terms_obj['execution'] = 'and'; + + /* + * add support for "NOT IN" operator + * + * @since 2.1 + */ + if ( ! empty( $single_tax_query['operator'] ) && 'NOT IN' === $single_tax_query['operator'] ) { + // If "NOT IN" than it should filter as must_not + $tax_must_not_filter[]['terms'] = $terms_obj; + } else { + // Use the AND operator if passed + if ( ! empty( $single_tax_query['operator'] ) && 'AND' === $single_tax_query['operator'] ) { + $terms_obj['execution'] = 'and'; + } + + // Add the tax query filter + $tax_filter[]['terms'] = $terms_obj; } - - // Add the tax query filter - $tax_filter[]['terms'] = $terms_obj; } } @@ -995,10 +1009,18 @@ public function format_args( $args ) { if ( ! empty( $args['tax_query']['relation'] ) && 'or' === strtolower( $args['tax_query']['relation'] ) ) { $relation = 'should'; } - - $filter['and'][]['bool'][$relation] = $tax_filter; + + $es_tax_query[$relation] = $tax_filter; } - + + if( ! empty( $tax_must_not_filter ) ) { + $es_tax_query['must_not'] = $tax_must_not_filter; + } + + if( ! empty( $es_tax_query ) ) { + $filter['and'][]['bool'] = $es_tax_query; + } + $use_filters = true; } diff --git a/tests/test-single-site.php b/tests/test-single-site.php index 62918b6d13..41401246ad 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -3237,4 +3237,54 @@ public function testSetupModules() { $this->assertTrue( $module->is_active() ); } + + /** + * Test Tax Query NOT IN operator + * + * @since 2.1 + */ + public function testTaxQueryNotIn() { + ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'tags_input' => array( 'one', 'two' ) ) ); + ep_create_and_sync_post( array( 'post_content' => 'findme test 2', 'tags_input' => array( 'one' ) ) ); + + ep_refresh_index(); + + $args = array( + 's' => 'findme', + 'tax_query' => array( + array( + 'taxonomy' => 'post_tag', + 'terms' => array( 'one' ), + 'field' => 'slug', + ) + ) + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 2, $query->post_count ); + $this->assertEquals( 2, $query->found_posts ); + + $args = array( + 's' => 'findme', + 'tax_query' => array( + array( + 'taxonomy' => 'post_tag', + 'terms' => array( 'one' ), + 'field' => 'slug', + ), + array( + 'taxonomy' => 'post_tag', + 'terms' => array( 'two' ), + 'field' => 'slug', + 'operator' => 'NOT IN', + ) + ) + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 1, $query->post_count ); + $this->assertEquals( 1, $query->found_posts ); + } } From b5d2a5e05d43596fd1619d423adc44a647233ced Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 21 Sep 2016 17:00:43 -0400 Subject: [PATCH 002/159] Start fixing php 5.2 errors; test php 5.2 in travis --- .travis.yml | 1 + classes/class-ep-config.php | 2 +- classes/class-ep-dashboard.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 463c427350..933a2f469f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: php php: - "5.6" - "5.3" + - "5.2" env: - WP_VERSION=latest WP_MULTISITE=0 diff --git a/classes/class-ep-config.php b/classes/class-ep-config.php index d36a274dde..99883d0ea3 100644 --- a/classes/class-ep-config.php +++ b/classes/class-ep-config.php @@ -77,7 +77,7 @@ public function is_indexing_wpcli() { */ public function get_host() { - if ( defined( 'EP_HOST' ) && ! empty( EP_HOST ) ) { + if ( defined( 'EP_HOST' ) && EP_HOST ) { $host = EP_HOST; } elseif ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $host = get_site_option( 'ep_host', false ); diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index db0f96623c..dedd81ff6d 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -155,7 +155,7 @@ public function action_bad_host_notice() { * If we are on the setting screen and a host has never been set in the options table or defined, then let's * assume this is our first time and not show an obvious message. */ - if ( ! empty( $_GET['page'] ) && 'elasticpress-settings' === $_GET['page'] && false === $options_host && ( ! defined( 'EP_HOST' ) || empty( EP_HOST ) ) ) { + if ( ! empty( $_GET['page'] ) && 'elasticpress-settings' === $_GET['page'] && false === $options_host && ( ! defined( 'EP_HOST' ) || ! EP_HOST ) ) { return; } From 533c30c6e864fab3388c71a300d41bc57839df03 Mon Sep 17 00:00:00 2001 From: Aaron Holbrook Date: Fri, 23 Sep 2016 11:05:41 -0500 Subject: [PATCH 003/159] Abort action queue meta sync if EP is not alive Fixes #582 --- classes/class-ep-sync-manager.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index 4a7c302aa9..3148622f54 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -94,6 +94,10 @@ public function action_index_sync_queue() { public function action_queue_meta_sync( $meta_id, $object_id, $meta_key, $meta_value ) { global $importer; + if ( ! ep_elasticsearch_alive() ) { + return; + } + // If we have an importer we must be doing an import - let's abort if ( ! empty( $importer ) ) { return; From 4d782a747cad232911e9b0688b443acd7a6f7a85 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Mon, 26 Sep 2016 02:28:06 +0530 Subject: [PATCH 004/159] Added support for "orderby rand" --- classes/class-ep-api.php | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index ae1e1c2700..92c28393ed 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1479,6 +1479,21 @@ public function format_args( $args ) { } else if ( ! empty( $args['ep_match_all'] ) || ! empty( $args['ep_integrate'] ) ) { $formatted_args['query']['match_all'] = array(); } + + /** + * Order by 'rand' support + * + * Ref: https://github.com/elastic/elasticsearch/issues/1170 + */ + if ( ! empty( $args['orderby'] ) ) { + $orderbys = $this->get_orderby_array( $args['orderby'] ); + if( in_array( 'rand', $orderbys ) ) { + $formatted_args_query = $formatted_args['query']; + $formatted_args['query'] = array(); + $formatted_args['query']['function_score']['query'] = $formatted_args_query; + $formatted_args['query']['function_score']['random_score'] = (object) array(); + } + } /** * If not set default to post. If search and not set, default to "any". @@ -1721,9 +1736,7 @@ protected function parse_order( $order ) { * @return array */ protected function parse_orderby( $orderbys, $default_order, $args ) { - if ( ! is_array( $orderbys ) ) { - $orderbys = explode( ' ', $orderbys ); - } + $orderbys = $this->get_orderby_array( $orderbys ); $sort = array(); @@ -1736,7 +1749,7 @@ protected function parse_orderby( $orderbys, $default_order, $args ) { $order = $default_order; } - if ( ! empty( $orderby_clause ) ) { + if ( ! empty( $orderby_clause ) && 'rand' !== $orderby_clause ) { if ( 'relevance' === $orderby_clause ) { $sort[] = array( '_score' => array( @@ -1801,6 +1814,22 @@ protected function parse_orderby( $orderbys, $default_order, $args ) { return $sort; } + + /** + * Get Order by args Array + * + * @param $orderbys + * + * @since 2.1 + * @return array + */ + protected function get_orderby_array( $orderbys ){ + if ( ! is_array( $orderbys ) ) { + $orderbys = explode( ' ', $orderbys ); + } + + return $orderbys; + } /** * This function checks if we can connect to Elasticsearch From 98bb3413daf5603d07864ffddf67c5402cfd4297 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Tue, 27 Sep 2016 14:21:38 -0400 Subject: [PATCH 005/159] Make sure we support PHP 5.2 --- modules/woocommerce/woocommerce.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/woocommerce/woocommerce.php b/modules/woocommerce/woocommerce.php index 388313cecd..88682de71d 100644 --- a/modules/woocommerce/woocommerce.php +++ b/modules/woocommerce/woocommerce.php @@ -347,8 +347,10 @@ function ep_wc_translate_args( $query ) { if ( ! empty( $orderby ) && 'rand' === $orderby ) { $query->set( 'orderby', false ); // Just order by relevance. } + + $s = $query->get( 's' ); - if ( empty( $query->get( 's' ) ) ) { + if ( empty( $s ) ) { $query->query_vars['ep_integrate'] = true; $query->query['ep_integrate'] = true; } else { From 9ae6742982713e7cd0839a99b02c0df4afde1a96 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 28 Sep 2016 11:11:40 -0400 Subject: [PATCH 006/159] Properly show syncing placeholder within syncing module --- assets/css/admin.css | 2 +- assets/css/admin.min.css | 2 +- assets/css/admin.scss | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/css/admin.css b/assets/css/admin.css index dbe2432a71..0731013ff3 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -141,7 +141,7 @@ .ep-module.module-active .syncing-placeholder { display: none; } -.ep-module.module-syncing button.syncing-placeholder { +.ep-module.module-syncing .syncing-placeholder { display: inline-block; } .ep-module.module-active .deactivate { diff --git a/assets/css/admin.min.css b/assets/css/admin.min.css index 6701fba83e..a823cbda2e 100644 --- a/assets/css/admin.min.css +++ b/assets/css/admin.min.css @@ -1 +1 @@ -.ep-modules,.wrap.intro{overflow:auto}.ep-header-menu{background-color:#fff;position:relative;margin-left:-20px;border-bottom:2px solid #ddd;z-index:2;padding:5px 20px}.progress-bar{margin-bottom:-5px;height:5px;position:absolute;bottom:0;left:0;background-color:#D84440}.ep-header-menu:after{content:' ';clear:both;display:block}.ep-header-menu img{float:left}.ep-header-menu .icons{float:right;padding-right:3px;height:39px;line-height:39px;display:inline-block}.ep-header-menu .icons .dashicons-update{font-size:29px;margin-right:10px;top:-3px}.ep-header-menu .icons a{font-size:27px;vertical-align:middle;color:inherit;position:relative;top:-3px;margin-left:8px;cursor:pointer}.ep-header-menu .sync-status,.module-message.module-error{color:#666;font-style:italic}.ep-header-menu .icons .dashicons-controls-pause,.ep-header-menu .icons .dashicons-controls-play,.ep-header-menu .icons .dashicons-no{top:1px;font-size:30px}.ep-header-menu .cancel-sync,.ep-header-menu .pause-sync,.ep-header-menu .resume-sync{display:none}.ep-header-menu .sync-status{display:none;bottom:-1px;position:relative;margin-right:8px;vertical-align:middle}.wrap .notice,.wrap>h2{z-index:2;position:relative}.ep-module.module-dependencies-unmet .postbox:after{content:' ';display:block;position:absolute;top:0;left:0;right:0;bottom:0;opacity:.4;background-color:#333}.error-overlay.cant-connect,.error-overlay.syncing{content:' ';display:block;position:fixed;top:46px;left:0;right:0;bottom:0;opacity:.6;background-color:#fff;z-index:1}@media (min-width:768px){.error-overlay,.error-overlay.cant-connect,.error-overlay.syncing{top:32px;left:160px}}.ep-module .postbox{margin-bottom:0}.ep-module .postbox .hndle{cursor:inherit}.ep-module{position:relative;vertical-align:top;margin-bottom:20px}.ep-module .module-message{text-align:center;padding:12px}.ep-module .deactivate,.ep-module .syncing-placeholder,.ep-module.module-active .activate,.ep-module.module-active .syncing-placeholder,.ep-module.module-active.module-syncing .deactivate,.ep-module.module-syncing .activate,.ep-module.module-syncing .deactivate{display:none}.ep-module.module-syncing button.syncing-placeholder{display:inline-block}.ep-module.module-active .deactivate{display:inline-block;position:relative}.ep-module .activate.processing:after,.ep-module .deactivate.processing:after{content:' ';vertical-align:middle;margin-left:1.4em;top:-.25em;border-radius:50%;width:1em;height:1em;display:inline-block;font-size:6px;position:relative;text-indent:-9999em;border-top:1.1em solid rgba(255,255,255,.2);border-right:1.1em solid rgba(255,255,255,.2);border-bottom:1.1em solid rgba(255,255,255,.2);border-left:1.1em solid #fff;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation:load8 1.1s infinite linear;animation:load8 1.1s infinite linear}.ep-module .collapse:after,.ep-module .learn-more:after{font-family:dashicons;font-size:1.5em;vertical-align:middle;top:-.07em;position:relative}.ep-module .deactivate.processing:after{border-top:1.1em solid rgba(180,180,180,.4);border-right:1.1em solid rgba(180,180,180,.4);border-bottom:1.1em solid rgba(180,180,180,.4);border-left:1.1em solid #666}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ep-module .inside{margin-bottom:0;border-bottom-width:2px;text-align:left}.ep-module .action{margin:6px 0;padding:0 12px 0 13px;text-align:right}.ep-module .long{display:none}.ep-module.show-all .long{display:block}.ep-module .collapse,.ep-module .learn-more{cursor:pointer}.ep-module.show-all .learn-more{display:none}.ep-module .learn-more:after{content:"\f140"}.ep-module .collapse:after{content:"\f142"}.setup-message{text-align:center;background-color:#e63e3b;margin:20px -20px 0;padding:2em 0;clear:both}.setup-message .setup-button{border-radius:5px;background-color:#d73c38;color:#f19ea4;display:inline-block;margin:0 .75em;text-decoration:none;padding:.75em 3em;box-shadow:1px 1px 3px 1px rgba(0,0,0,.25)}.setup-message .setup-button-primary{background-color:#fff;color:#d84440}.modules-screenshot{display:none}.wrap h2{color:#888;line-height:1.75;margin:.5em 0 .75em}.intro h2{padding:9px 15px 4px 0}.wrap.intro{margin-top:30px;margin-bottom:30px}.wrap.intro .error,.wrap.intro .is-dismissible,.wrap.intro .notice,.wrap.intro .updated{display:none!important}#wpbody #update-nag,#wpbody .update-nag{margin-left:-20px;width:100%;margin-top:0;margin-bottom:20px}@media (min-width:768px){.intro .left{float:left;width:30%}.modules-screenshot{margin:0 auto;max-width:1000px;width:70%;display:block;height:auto;float:right}.ep-modules .left,.ep-modules .right{display:block;box-sizing:border-box;width:50%}.ep-modules .left{float:left;padding-right:10px}.ep-modules .right{float:right;padding-left:10px}.ep-module .module-message{float:left;padding:.5em 0 0;vertical-align:middle;display:inline-block}} \ No newline at end of file +.ep-modules,.wrap.intro{overflow:auto}.ep-header-menu{background-color:#fff;position:relative;margin-left:-20px;border-bottom:2px solid #ddd;z-index:2;padding:5px 20px}.progress-bar{margin-bottom:-5px;height:5px;position:absolute;bottom:0;left:0;background-color:#D84440}.ep-header-menu:after{content:' ';clear:both;display:block}.ep-header-menu img{float:left}.ep-header-menu .icons{float:right;padding-right:3px;height:39px;line-height:39px;display:inline-block}.ep-header-menu .icons .dashicons-update{font-size:29px;margin-right:10px;top:-3px}.ep-header-menu .icons a{font-size:27px;vertical-align:middle;color:inherit;position:relative;top:-3px;margin-left:8px;cursor:pointer}.ep-header-menu .sync-status,.module-message.module-error{color:#666;font-style:italic}.ep-header-menu .icons .dashicons-controls-pause,.ep-header-menu .icons .dashicons-controls-play,.ep-header-menu .icons .dashicons-no{top:1px;font-size:30px}.ep-header-menu .cancel-sync,.ep-header-menu .pause-sync,.ep-header-menu .resume-sync{display:none}.ep-header-menu .sync-status{display:none;bottom:-1px;position:relative;margin-right:8px;vertical-align:middle}.wrap .notice,.wrap>h2{z-index:2;position:relative}.ep-module.module-dependencies-unmet .postbox:after{content:' ';display:block;position:absolute;top:0;left:0;right:0;bottom:0;opacity:.4;background-color:#333}.error-overlay.cant-connect,.error-overlay.syncing{content:' ';display:block;position:fixed;top:46px;left:0;right:0;bottom:0;opacity:.6;background-color:#fff;z-index:1}@media (min-width:768px){.error-overlay,.error-overlay.cant-connect,.error-overlay.syncing{top:32px;left:160px}}.ep-module .postbox{margin-bottom:0}.ep-module .postbox .hndle{cursor:inherit}.ep-module{position:relative;vertical-align:top;margin-bottom:20px}.ep-module .module-message{text-align:center;padding:12px}.ep-module .deactivate,.ep-module .syncing-placeholder,.ep-module.module-active .activate,.ep-module.module-active .syncing-placeholder,.ep-module.module-active.module-syncing .deactivate,.ep-module.module-syncing .activate,.ep-module.module-syncing .deactivate{display:none}.ep-module.module-syncing .syncing-placeholder{display:inline-block}.ep-module.module-active .deactivate{display:inline-block;position:relative}.ep-module .activate.processing:after,.ep-module .deactivate.processing:after{content:' ';vertical-align:middle;margin-left:1.4em;top:-.25em;border-radius:50%;width:1em;height:1em;display:inline-block;font-size:6px;position:relative;text-indent:-9999em;border-top:1.1em solid rgba(255,255,255,.2);border-right:1.1em solid rgba(255,255,255,.2);border-bottom:1.1em solid rgba(255,255,255,.2);border-left:1.1em solid #fff;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation:load8 1.1s infinite linear;animation:load8 1.1s infinite linear}.ep-module .collapse:after,.ep-module .learn-more:after{font-family:dashicons;font-size:1.5em;vertical-align:middle;top:-.07em;position:relative}.ep-module .deactivate.processing:after{border-top:1.1em solid rgba(180,180,180,.4);border-right:1.1em solid rgba(180,180,180,.4);border-bottom:1.1em solid rgba(180,180,180,.4);border-left:1.1em solid #666}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ep-module .inside{margin-bottom:0;border-bottom-width:2px;text-align:left}.ep-module .action{margin:6px 0;padding:0 12px 0 13px;text-align:right}.ep-module .long{display:none}.ep-module.show-all .long{display:block}.ep-module .collapse,.ep-module .learn-more{cursor:pointer}.ep-module.show-all .learn-more{display:none}.ep-module .learn-more:after{content:"\f140"}.ep-module .collapse:after{content:"\f142"}.setup-message{text-align:center;background-color:#e63e3b;margin:20px -20px 0;padding:2em 0;clear:both}.setup-message .setup-button{border-radius:5px;background-color:#d73c38;color:#f19ea4;display:inline-block;margin:0 .75em;text-decoration:none;padding:.75em 3em;box-shadow:1px 1px 3px 1px rgba(0,0,0,.25)}.setup-message .setup-button-primary{background-color:#fff;color:#d84440}.modules-screenshot{display:none}.wrap h2{color:#888;line-height:1.75;margin:.5em 0 .75em}.intro h2{padding:9px 15px 4px 0}.wrap.intro{margin-top:30px;margin-bottom:30px}.wrap.intro .error,.wrap.intro .is-dismissible,.wrap.intro .notice,.wrap.intro .updated{display:none!important}#wpbody #update-nag,#wpbody .update-nag{margin-left:-20px;width:100%;margin-top:0;margin-bottom:20px}@media (min-width:768px){.intro .left{float:left;width:30%}.modules-screenshot{margin:0 auto;max-width:1000px;width:70%;display:block;height:auto;float:right}.ep-modules .left,.ep-modules .right{display:block;box-sizing:border-box;width:50%}.ep-modules .left{float:left;padding-right:10px}.ep-modules .right{float:right;padding-left:10px}.ep-module .module-message{float:left;padding:.5em 0 0;vertical-align:middle;display:inline-block}} \ No newline at end of file diff --git a/assets/css/admin.scss b/assets/css/admin.scss index 66754edded..275bd8aa69 100644 --- a/assets/css/admin.scss +++ b/assets/css/admin.scss @@ -169,7 +169,7 @@ $tablet-width: 768px; display: none; } -.ep-module.module-syncing button.syncing-placeholder { +.ep-module.module-syncing .syncing-placeholder { display: inline-block; } From f8894b226fe09f10bacd3557a40ab038ac170152 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 28 Sep 2016 11:28:56 -0400 Subject: [PATCH 007/159] Fix php 5.3 error --- bin/wp-cli.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index 394ef66b46..e1661e44b1 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -796,7 +796,9 @@ public function stop_the_insanity() { * @since 0.9.3 */ private function _connect_check() { - if ( empty( ep_get_host() ) ) { + $host = ep_get_host(); + + if ( empty( $host) ) { WP_CLI::error( __( 'There is no Elasticsearch host set up. Either add one through the dashboard or define one in wp-config.php', 'elasticpress' ) ); } elseif ( ! ep_elasticsearch_can_connect() ) { WP_CLI::error( __( 'Unable to reach Elasticsearch Server! Check that service is running.', 'elasticpress' ) ); From aded699972fd87fcbc41312770feb5c9caece724 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 28 Sep 2016 12:52:32 -0400 Subject: [PATCH 008/159] Revert "Add support for tax_query NOT IN operator" --- classes/class-ep-api.php | 42 ++++++++------------------------ tests/test-single-site.php | 50 -------------------------------------- 2 files changed, 10 insertions(+), 82 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 2758d09652..ae1e1c2700 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -963,10 +963,6 @@ public function format_args( $args ) { if ( ! empty( $args['tax_query'] ) ) { $tax_filter = array(); - $tax_must_not_filter = array(); - - // Main tax_query array for ES - $es_tax_query = array(); foreach( $args['tax_query'] as $single_tax_query ) { if ( ! empty( $single_tax_query['terms'] ) ) { @@ -982,24 +978,14 @@ public function format_args( $args ) { $terms_obj = array( 'terms.' . $single_tax_query['taxonomy'] . '.' . $field => $terms, ); - - /* - * add support for "NOT IN" operator - * - * @since 2.1 - */ - if ( ! empty( $single_tax_query['operator'] ) && 'NOT IN' === $single_tax_query['operator'] ) { - // If "NOT IN" than it should filter as must_not - $tax_must_not_filter[]['terms'] = $terms_obj; - } else { - // Use the AND operator if passed - if ( ! empty( $single_tax_query['operator'] ) && 'AND' === $single_tax_query['operator'] ) { - $terms_obj['execution'] = 'and'; - } - - // Add the tax query filter - $tax_filter[]['terms'] = $terms_obj; + + // Use the AND operator if passed + if ( ! empty( $single_tax_query['operator'] ) && 'AND' === $single_tax_query['operator'] ) { + $terms_obj['execution'] = 'and'; } + + // Add the tax query filter + $tax_filter[]['terms'] = $terms_obj; } } @@ -1009,18 +995,10 @@ public function format_args( $args ) { if ( ! empty( $args['tax_query']['relation'] ) && 'or' === strtolower( $args['tax_query']['relation'] ) ) { $relation = 'should'; } - - $es_tax_query[$relation] = $tax_filter; - } - - if( ! empty( $tax_must_not_filter ) ) { - $es_tax_query['must_not'] = $tax_must_not_filter; - } - - if( ! empty( $es_tax_query ) ) { - $filter['and'][]['bool'] = $es_tax_query; + + $filter['and'][]['bool'][$relation] = $tax_filter; } - + $use_filters = true; } diff --git a/tests/test-single-site.php b/tests/test-single-site.php index c637d36b92..e35f3b0c42 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -3237,54 +3237,4 @@ public function testSetupModules() { $this->assertTrue( $module->is_active() ); } - - /** - * Test Tax Query NOT IN operator - * - * @since 2.1 - */ - public function testTaxQueryNotIn() { - ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'tags_input' => array( 'one', 'two' ) ) ); - ep_create_and_sync_post( array( 'post_content' => 'findme test 2', 'tags_input' => array( 'one' ) ) ); - - ep_refresh_index(); - - $args = array( - 's' => 'findme', - 'tax_query' => array( - array( - 'taxonomy' => 'post_tag', - 'terms' => array( 'one' ), - 'field' => 'slug', - ) - ) - ); - - $query = new WP_Query( $args ); - - $this->assertEquals( 2, $query->post_count ); - $this->assertEquals( 2, $query->found_posts ); - - $args = array( - 's' => 'findme', - 'tax_query' => array( - array( - 'taxonomy' => 'post_tag', - 'terms' => array( 'one' ), - 'field' => 'slug', - ), - array( - 'taxonomy' => 'post_tag', - 'terms' => array( 'two' ), - 'field' => 'slug', - 'operator' => 'NOT IN', - ) - ) - ); - - $query = new WP_Query( $args ); - - $this->assertEquals( 1, $query->post_count ); - $this->assertEquals( 1, $query->found_posts ); - } } From de8ae80129de4d60c7f5639bcaf0db3a52b7d539 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 28 Sep 2016 12:55:02 -0400 Subject: [PATCH 009/159] Increment version --- elasticpress.php | 4 ++-- readme.txt | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/elasticpress.php b/elasticpress.php index 69ed45d62d..0a340f0d8d 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -3,7 +3,7 @@ /** * Plugin Name: ElasticPress * Description: A fast and flexible search and query engine for WordPress. - * Version: 2.1 + * Version: 2.1.1 * Author: Taylor Lovett, Matt Gross, Aaron Holbrook, 10up * Author URI: http://10up.com * License: GPLv2 or later @@ -22,7 +22,7 @@ define( 'EP_URL', plugin_dir_url( __FILE__ ) ); define( 'EP_PATH', plugin_dir_path( __FILE__ ) ); -define( 'EP_VERSION', '2.1' ); +define( 'EP_VERSION', '2.1.1' ); define( 'EP_MODULES_DIR', dirname( __FILE__ ) . '/modules' ); require_once( 'classes/class-ep-config.php' ); diff --git a/readme.txt b/readme.txt index d7862e4a17..0e2ccf5f85 100644 --- a/readme.txt +++ b/readme.txt @@ -34,6 +34,11 @@ Please refer to [Github](https://github.com/10up/ElasticPress) for detailed usag == Changelog == += 2.1.1 = + +* Fix PHP 5.3 errors +* Properly show syncing button module placeholder during sync + = 2.1 = * Redo UI From 3b865ee2d56340ca4dfc12b6be25b483626af8c9 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Fri, 30 Sep 2016 02:35:50 +0530 Subject: [PATCH 010/159] Revert "Revert "Add support for tax_query NOT IN operator"" This reverts commit aded699972fd87fcbc41312770feb5c9caece724. --- classes/class-ep-api.php | 42 ++++++++++++++++++++++++-------- tests/test-single-site.php | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index ae1e1c2700..2758d09652 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -963,6 +963,10 @@ public function format_args( $args ) { if ( ! empty( $args['tax_query'] ) ) { $tax_filter = array(); + $tax_must_not_filter = array(); + + // Main tax_query array for ES + $es_tax_query = array(); foreach( $args['tax_query'] as $single_tax_query ) { if ( ! empty( $single_tax_query['terms'] ) ) { @@ -978,14 +982,24 @@ public function format_args( $args ) { $terms_obj = array( 'terms.' . $single_tax_query['taxonomy'] . '.' . $field => $terms, ); - - // Use the AND operator if passed - if ( ! empty( $single_tax_query['operator'] ) && 'AND' === $single_tax_query['operator'] ) { - $terms_obj['execution'] = 'and'; + + /* + * add support for "NOT IN" operator + * + * @since 2.1 + */ + if ( ! empty( $single_tax_query['operator'] ) && 'NOT IN' === $single_tax_query['operator'] ) { + // If "NOT IN" than it should filter as must_not + $tax_must_not_filter[]['terms'] = $terms_obj; + } else { + // Use the AND operator if passed + if ( ! empty( $single_tax_query['operator'] ) && 'AND' === $single_tax_query['operator'] ) { + $terms_obj['execution'] = 'and'; + } + + // Add the tax query filter + $tax_filter[]['terms'] = $terms_obj; } - - // Add the tax query filter - $tax_filter[]['terms'] = $terms_obj; } } @@ -995,10 +1009,18 @@ public function format_args( $args ) { if ( ! empty( $args['tax_query']['relation'] ) && 'or' === strtolower( $args['tax_query']['relation'] ) ) { $relation = 'should'; } - - $filter['and'][]['bool'][$relation] = $tax_filter; + + $es_tax_query[$relation] = $tax_filter; } - + + if( ! empty( $tax_must_not_filter ) ) { + $es_tax_query['must_not'] = $tax_must_not_filter; + } + + if( ! empty( $es_tax_query ) ) { + $filter['and'][]['bool'] = $es_tax_query; + } + $use_filters = true; } diff --git a/tests/test-single-site.php b/tests/test-single-site.php index e35f3b0c42..c637d36b92 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -3237,4 +3237,54 @@ public function testSetupModules() { $this->assertTrue( $module->is_active() ); } + + /** + * Test Tax Query NOT IN operator + * + * @since 2.1 + */ + public function testTaxQueryNotIn() { + ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'tags_input' => array( 'one', 'two' ) ) ); + ep_create_and_sync_post( array( 'post_content' => 'findme test 2', 'tags_input' => array( 'one' ) ) ); + + ep_refresh_index(); + + $args = array( + 's' => 'findme', + 'tax_query' => array( + array( + 'taxonomy' => 'post_tag', + 'terms' => array( 'one' ), + 'field' => 'slug', + ) + ) + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 2, $query->post_count ); + $this->assertEquals( 2, $query->found_posts ); + + $args = array( + 's' => 'findme', + 'tax_query' => array( + array( + 'taxonomy' => 'post_tag', + 'terms' => array( 'one' ), + 'field' => 'slug', + ), + array( + 'taxonomy' => 'post_tag', + 'terms' => array( 'two' ), + 'field' => 'slug', + 'operator' => 'NOT IN', + ) + ) + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 1, $query->post_count ); + $this->assertEquals( 1, $query->found_posts ); + } } From dc2f46ac69603f3722f42c1c7b5f52f21a2bbd80 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Mon, 3 Oct 2016 03:24:36 +0530 Subject: [PATCH 011/159] test case for orderby random --- tests/test-single-site.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index e35f3b0c42..62b54f5313 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -1974,6 +1974,32 @@ public function testSearchDefaultOrderbyASCOrderQuery() { $this->assertEquals( 'ordertestt', $query->posts[0]->post_title ); $this->assertEquals( 'Ordertest', $query->posts[1]->post_title ); } + + /** + * Test orderby random + * + * @since 2.1.1 + */ + public function testRandOrderby() { + ep_create_and_sync_post( array( 'post_title' => 'ordertest 1' ) ); + ep_create_and_sync_post( array( 'post_title' => 'ordertest 2' ) ); + ep_create_and_sync_post( array( 'post_title' => 'ordertest 3' ) ); + + ep_refresh_index(); + + $args = array( + 'ep_integrate' => true, + 'orderby' => 'rand', + ); + + $query = new WP_Query( $args ); + + /* Since it's test for random order, can't check against exact post ID or content + but only found posts and post count. + */ + $this->assertEquals( 3, $query->post_count ); + $this->assertEquals( 3, $query->found_posts ); + } /** * Test a normal post trash From 333616cd79524868cbc1f8e24c08d7ca24c917b8 Mon Sep 17 00:00:00 2001 From: mustafauysal Date: Thu, 13 Oct 2016 11:51:26 +0300 Subject: [PATCH 012/159] removing unused function ep_elasticsearch_alive --- classes/class-ep-api.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 2758d09652..06b45015ec 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -2270,10 +2270,6 @@ function ep_elasticpress_enabled( $query ) { return EP_API::factory()->elasticpress_enabled( $query ); } -function ep_elasticsearch_alive( $host = null ) { - return EP_API::factory()->elasticsearch_alive( $host ); -} - function ep_index_exists( $index_name = null ) { return EP_API::factory()->index_exists( $index_name ); } From d079b19a00feb76654ec821fe9ea9ef7313dd962 Mon Sep 17 00:00:00 2001 From: mustafauysal Date: Thu, 13 Oct 2016 16:10:05 +0300 Subject: [PATCH 013/159] Revert "removing unused function ep_elasticsearch_alive" This reverts commit 333616cd79524868cbc1f8e24c08d7ca24c917b8. --- classes/class-ep-api.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 06b45015ec..2758d09652 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -2270,6 +2270,10 @@ function ep_elasticpress_enabled( $query ) { return EP_API::factory()->elasticpress_enabled( $query ); } +function ep_elasticsearch_alive( $host = null ) { + return EP_API::factory()->elasticsearch_alive( $host ); +} + function ep_index_exists( $index_name = null ) { return EP_API::factory()->index_exists( $index_name ); } From 94856e6b5d41dfb04860971032fb2d6cd7c91af2 Mon Sep 17 00:00:00 2001 From: mustafauysal Date: Thu, 13 Oct 2016 16:26:59 +0300 Subject: [PATCH 014/159] `ep_elasticsearch_alive` deprecated --- classes/class-ep-api.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 2758d09652..3aeca74dc0 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -2271,7 +2271,9 @@ function ep_elasticpress_enabled( $query ) { } function ep_elasticsearch_alive( $host = null ) { - return EP_API::factory()->elasticsearch_alive( $host ); + _deprecated_function( __FUNCTION__, 'ElasticPress 2.1', 'ep_elasticsearch_can_connect()' ); + + return EP_API::factory()->elasticsearch_can_connect(); } function ep_index_exists( $index_name = null ) { From d1f8ffcbb2bb95f78ffaad2a208ac798ab3b63f9 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Thu, 13 Oct 2016 23:24:07 +0530 Subject: [PATCH 015/159] Do not add any WooCommerce related action/filter if WooCommerce is not active --- modules/woocommerce/woocommerce.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/woocommerce/woocommerce.php b/modules/woocommerce/woocommerce.php index 88682de71d..4742cc8ad4 100644 --- a/modules/woocommerce/woocommerce.php +++ b/modules/woocommerce/woocommerce.php @@ -506,19 +506,21 @@ function ep_wc_bypass_order_permissions_check( $override, $post_id ) { * @since 2.1 */ function ep_wc_setup() { - add_filter( 'ep_sync_insert_permissions_bypass', 'ep_wc_bypass_order_permissions_check', 10, 2 ); - add_filter( 'ep_elasticpress_enabled', 'ep_wc_blacklist_coupons', 10 ,2 ); - add_filter( 'ep_indexable_post_types', 'ep_wc_post_types', 10, 1 ); - add_filter( 'ep_prepare_meta_allowed_protected_keys', 'ep_wc_whitelist_meta_keys', 10, 2 ); - add_filter( 'woocommerce_shop_order_search_fields', 'ep_wc_shop_order_search_fields' ); - add_filter( 'woocommerce_layered_nav_query_post_ids', 'ep_wc_convert_post_object_to_id', 10, 4 ); - add_filter( 'woocommerce_unfiltered_product_ids', 'ep_wc_convert_post_object_to_id', 10, 4 ); - add_filter( 'ep_sync_taxonomies', 'ep_wc_whitelist_taxonomies', 10, 2 ); - add_filter( 'ep_post_sync_args_post_prepare_meta', 'ep_wc_remove_legacy_meta', 10, 2 ); - add_action( 'pre_get_posts', 'ep_wc_translate_args', 11, 1 ); - add_filter( 'ep_admin_wp_query_integration', '__return_true' ); - add_filter( 'ep_indexable_post_status', 'ep_admin_get_statuses' ); - add_filter( 'ep_elasticpress_enabled', 'ep_integrate_search_queries', 10, 2 ); + if( function_exists( 'WC' ) ) { + add_filter( 'ep_sync_insert_permissions_bypass', 'ep_wc_bypass_order_permissions_check', 10, 2 ); + add_filter( 'ep_elasticpress_enabled', 'ep_wc_blacklist_coupons', 10 ,2 ); + add_filter( 'ep_indexable_post_types', 'ep_wc_post_types', 10, 1 ); + add_filter( 'ep_prepare_meta_allowed_protected_keys', 'ep_wc_whitelist_meta_keys', 10, 2 ); + add_filter( 'woocommerce_shop_order_search_fields', 'ep_wc_shop_order_search_fields' ); + add_filter( 'woocommerce_layered_nav_query_post_ids', 'ep_wc_convert_post_object_to_id', 10, 4 ); + add_filter( 'woocommerce_unfiltered_product_ids', 'ep_wc_convert_post_object_to_id', 10, 4 ); + add_filter( 'ep_sync_taxonomies', 'ep_wc_whitelist_taxonomies', 10, 2 ); + add_filter( 'ep_post_sync_args_post_prepare_meta', 'ep_wc_remove_legacy_meta', 10, 2 ); + add_action( 'pre_get_posts', 'ep_wc_translate_args', 11, 1 ); + add_filter( 'ep_admin_wp_query_integration', '__return_true' ); + add_filter( 'ep_indexable_post_status', 'ep_admin_get_statuses' ); + add_filter( 'ep_elasticpress_enabled', 'ep_integrate_search_queries', 10, 2 ); + } } /** From ea2e76d1fd8a37aeb868610b51a402b8c086b88f Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Fri, 14 Oct 2016 23:10:08 +0530 Subject: [PATCH 016/159] Set search fields which are provided in query arguments otherwise set default search fields for Elasticsearch query --- modules/search/search.php | 27 ++++++++++++++++----------- tests/test-single-site.php | 13 +++++++++++++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/modules/search/search.php b/modules/search/search.php index dc96d93c24..25ce2be84c 100644 --- a/modules/search/search.php +++ b/modules/search/search.php @@ -85,17 +85,22 @@ function ep_improve_default_search( $query ) { if ( ! ep_elasticpress_enabled( $query ) || ! $query->is_search() ) { return; } - - $query->set( 'search_fields', array( - 'post_title', - 'post_content', - 'post_excerpt', - 'author_name', - 'taxonomies' => array( - 'post_tag', - 'category', - ), - ) ); + + $search_fields = $query->get( 'search_fields' ); + + // Set search fields if they are not set + if( empty( $search_fields ) ) { + $query->set( 'search_fields', array( + 'post_title', + 'post_content', + 'post_excerpt', + 'author_name', + 'taxonomies' => array( + 'post_tag', + 'category', + ), + ) ); + } } /** diff --git a/tests/test-single-site.php b/tests/test-single-site.php index c637d36b92..515ca0df97 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -1352,6 +1352,19 @@ public function testSearchMetaQuery() { $this->assertEquals( 2, $query->post_count ); $this->assertEquals( 2, $query->found_posts ); + + // Only check for fields which are provided in search_fields. + $args = array( + 's' => 'findme', + 'search_fields' => array( + 'meta' => 'test_key' + ), + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 1, $query->post_count ); + $this->assertEquals( 1, $query->found_posts ); } /** From d3d80e708e0b342558151bba2bb44b28dba4fcea Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 27 Oct 2016 14:14:47 -0400 Subject: [PATCH 017/159] Update github banner --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ae7a2896d..e3817aab15 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ElasticPress, a fast and flexible search and query engine for WordPress, enables ElasticPress is module based so you can pick and choose what you need. The plugin even contains modules for popular plugins.

- +

## How Does it Work From a557fecee86ae768d2cb216bc89345dac54d1d1e Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Fri, 28 Oct 2016 09:07:27 +0530 Subject: [PATCH 018/159] Fix admin gui locked if wp cli indexing fails by using transient --- bin/wp-cli.php | 55 ++++++++++++++++++++++++++++++---- classes/class-ep-dashboard.php | 4 +-- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index e1661e44b1..88a9b0c3c6 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -29,6 +29,27 @@ class ElasticPress_CLI_Command extends WP_CLI_Command { * @since 1.7 */ private $failed_posts_message = array(); + + /** + * Holds timestamp of last time transient was set + * + * @since 2.1.1 + */ + private $last_transient_set_time = false; + + /** + * Holds whether it's network transient or not + * + * @since 2.1.1 + */ + private $is_network_transient = false; + + /** + * Holds time until transient expires + * + * @since 2.1.1 + */ + private $transient_expiration = 15 * MINUTE_IN_SECONDS; /** * Activate a module. @@ -338,12 +359,15 @@ public function index( $args, $assoc_args ) { do_action( 'ep_wp_cli_pre_index', $args, $assoc_args ); if ( isset( $assoc_args['network-wide'] ) && is_multisite() ) { - update_site_option( 'ep_index_meta', array( 'wpcli' => true ) ); + $this->is_network_transient = true; + set_site_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); } else { - update_option( 'ep_index_meta', array( 'wpcli' => true ) ); + set_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); } timer_start(); + + $this->last_transient_set_time = time(); // Run setup if flag was passed if ( isset( $assoc_args['setup'] ) && true === $assoc_args['setup'] ) { @@ -398,10 +422,10 @@ public function index( $args, $assoc_args ) { WP_CLI::log( WP_CLI::colorize( '%Y' . __( 'Total time elapsed: ', 'elasticpress' ) . '%N' . timer_stop() ) ); - if ( isset( $assoc_args['network-wide'] ) && is_multisite() ) { - delete_site_option( 'ep_index_meta' ); + if ( $this->is_network_transient ) { + delete_site_transient( 'ep_index_meta' ); } else { - delete_option( 'ep_index_meta' ); + delete_transient( 'ep_index_meta' ); } WP_CLI::success( __( 'Done!', 'elasticpress' ) ); @@ -480,6 +504,8 @@ private function _index_helper( $args ) { if ( $no_bulk ) { // index the posts one-by-one. not sure why someone may want to do this. $result = ep_sync_post( get_the_ID() ); + + $this->maybe_reset_transient(); do_action( 'ep_cli_post_index', get_the_ID() ); } else { @@ -622,6 +648,8 @@ private function bulk_index( $show_bulk_errors = false ) { // decode the response $response = ep_bulk_index_posts( $body ); + + $this->maybe_reset_transient(); do_action( 'ep_cli_post_bulk_index', $this->posts ); @@ -804,4 +832,21 @@ private function _connect_check() { WP_CLI::error( __( 'Unable to reach Elasticsearch Server! Check that service is running.', 'elasticpress' ) ); } } + + /** + * Maybe reset transient while indexing + * + * @since 2.1.1 + */ + private function maybe_reset_transient() { + if( ( time() - $this->last_transient_set_time ) >= ( $this->transient_expiration ) ) { + $this->last_transient_set_time = time(); + + if( $this->is_network_transient ) { + set_site_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); + } else { + set_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); + } + } + } } \ No newline at end of file diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index dedd81ff6d..ee148be80f 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -458,9 +458,9 @@ public function action_admin_enqueue_scripts() { $data = array( 'nonce' => wp_create_nonce( 'ep_nonce' ) ); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $index_meta = get_site_option( 'ep_index_meta' ); + $index_meta = get_site_transient( 'ep_index_meta' ); } else { - $index_meta = get_option( 'ep_index_meta' ); + $index_meta = get_transient( 'ep_index_meta' ); } if ( ! empty( $index_meta ) ) { From dbc3927e432df64c19eab868e53e99ae06ea3e6f Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Mon, 31 Oct 2016 01:01:16 +0530 Subject: [PATCH 019/159] Fix: when only related posts module is active, it does not work --- modules/related-posts/related-posts.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/related-posts/related-posts.php b/modules/related-posts/related-posts.php index 99a9d3caca..7242af40c1 100644 --- a/modules/related-posts/related-posts.php +++ b/modules/related-posts/related-posts.php @@ -59,7 +59,8 @@ function ep_find_related( $post_id, $return = 4 ) { $args = array( 'more_like' => $post_id, 'posts_per_page' => $return, - 's' => '' + 's' => '', + 'ep_integrate' => true, ); $query = new WP_Query( apply_filters( 'ep_find_related_args', $args ) ); From c59c54b18c42a23ab748e3cc6815549a053894dd Mon Sep 17 00:00:00 2001 From: Kristoffer Svanmark Date: Mon, 31 Oct 2016 11:10:50 +0100 Subject: [PATCH 020/159] #613 - Introduce new method to check if a post is indexable in action_sync_on_update --- classes/class-ep-sync-manager.php | 46 +++++++++++++++++++------------ 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index 4a7c302aa9..b15a842afe 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -21,7 +21,7 @@ public function __construct() { } /** * Save posts for indexing later - * + * * @since 2.0 * @var array */ @@ -45,7 +45,7 @@ public function setup() { add_action( 'added_post_meta', array( $this, 'action_queue_meta_sync' ), 10, 4 ); add_action( 'shutdown', array( $this, 'action_index_sync_queue' ) ); } - + /** * Remove actions and filters * @@ -84,7 +84,7 @@ public function action_index_sync_queue() { /** * When whitelisted meta is updated, queue the post for reindex - * + * * @param int $meta_id * @param int $object_id * @param string $meta_key @@ -98,7 +98,7 @@ public function action_queue_meta_sync( $meta_id, $object_id, $meta_key, $meta_v if ( ! empty( $importer ) ) { return; } - + $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $object_id ); @@ -173,7 +173,7 @@ public function action_sync_on_update( $post_ID ) { if ( ! empty( $importer ) ) { return; } - + $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $post_ID ); @@ -196,23 +196,35 @@ public function action_sync_on_update( $post_ID ) { return; } - // Our post was published, but is no longer, so let's remove it from the Elasticsearch index - if ( ! in_array( $post->post_status, $indexable_post_statuses ) ) { - $this->action_delete_post( $post_ID ); + // If post is indexable go ahead and index it + // If post not indexable, remove it's existance from index + if ( $this->is_post_indexable( $post_ID ) ) { + do_action( 'ep_sync_on_transition', $post_ID ); + $this->sync_post( $post_ID, false ); } else { - $post_type = get_post_type( $post_ID ); - - $indexable_post_types = ep_get_indexable_post_types(); - - if ( in_array( $post_type, $indexable_post_types ) ) { + $this->action_delete_post( $post_ID ); + } + } - do_action( 'ep_sync_on_transition', $post_ID ); + /** + * Check if post matches the criterias to be indexed + * + * @param int $post_ID The post id to check + * @return boolean + */ + public function is_post_indexable( $post_ID ) { + $indexable_post_types = ep_get_indexable_post_types(); + $indexable_post_statuses = ep_get_indexable_post_status(); + $post_type = get_post_type( $post_ID ); + $post_status = get_post_status( $post_ID ); - $this->sync_post( $post_ID, false ); - } + if ( ! in_array( $post_type, $indexable_post_types ) || ! in_array( $post_status, $indexable_post_statuses ) ) { + return false; } + + return true; } - + /** * Return a singleton instance of the current class * From 3438928ea67e67065ef55394f5b3e585c9c2a0c4 Mon Sep 17 00:00:00 2001 From: Kristoffer Svanmark Date: Mon, 31 Oct 2016 11:13:54 +0100 Subject: [PATCH 021/159] #613 - Some fine-tuning in the action_sync_on_update --- classes/class-ep-sync-manager.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index b15a842afe..b647cbb2f0 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -174,10 +174,7 @@ public function action_sync_on_update( $post_ID ) { return; } - $indexable_post_statuses = ep_get_indexable_post_status(); - $post_type = get_post_type( $post_ID ); - - if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || 'revision' === $post_type ) { + if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || wp_is_post_revision( $post_ID ) ) { // Bypass saving if doing autosave or post type is revision return; } @@ -189,13 +186,6 @@ public function action_sync_on_update( $post_ID ) { } } - $post = get_post( $post_ID ); - - // If the post is an auto-draft - let's abort. - if ( 'auto-draft' == $post->post_status ) { - return; - } - // If post is indexable go ahead and index it // If post not indexable, remove it's existance from index if ( $this->is_post_indexable( $post_ID ) ) { @@ -218,7 +208,13 @@ public function is_post_indexable( $post_ID ) { $post_type = get_post_type( $post_ID ); $post_status = get_post_status( $post_ID ); - if ( ! in_array( $post_type, $indexable_post_types ) || ! in_array( $post_status, $indexable_post_statuses ) ) { + if ( + ! in_array( $post_type, $indexable_post_types ) + || + ! in_array( $post_status, $indexable_post_statuses ) + || + $post_status == 'auto-draft' + ) { return false; } From cf9ad946e484ddab52432a307e3d951efcec36de Mon Sep 17 00:00:00 2001 From: Jerry Volfson Date: Mon, 31 Oct 2016 16:55:20 -0700 Subject: [PATCH 022/159] When using the 'ep_wp_query_search_cached_posts' filter, if it returns a non empty response $ep_query is never set and as a result line 303 generates a PHP warning indicating so. Added a check to see if it is set before attempting to use it. --- classes/class-ep-wp-query-integration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/class-ep-wp-query-integration.php b/classes/class-ep-wp-query-integration.php index 6dd429b474..bbc5a15417 100644 --- a/classes/class-ep-wp-query-integration.php +++ b/classes/class-ep-wp-query-integration.php @@ -300,7 +300,7 @@ public function filter_posts_request( $request, $query ) { $this->posts_by_query[spl_object_hash( $query )] = $new_posts; - do_action( 'ep_wp_query_search', $new_posts, $ep_query, $query ); + do_action( 'ep_wp_query_search', $new_posts, (isset($ep_query) ? $ep_query : null), $query ); global $wpdb; From 8f3e5aa150b46b781f4515882d2ae9f563b7cf51 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Tue, 1 Nov 2016 19:55:23 +0530 Subject: [PATCH 023/159] Add ep_search function for backward compatibility --- classes/class-ep-api.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 3aeca74dc0..72653306d1 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -2320,3 +2320,16 @@ function ep_parse_site_id( $index_name ) { return EP_API::factory()->parse_site_id( $index_name ); } +if( ! function_exists( 'ep_search' ) ) { + /** + * Backward compatibility for ep_search + * + * @param $args + * @param string $scope + * + * @return array + */ + function ep_search( $args, $scope = 'current' ) { + return ep_query( $args, array(), $scope ); + } +} From 0dee81241280b4c94cb45217a4baa1cbf8a55c26 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Wed, 2 Nov 2016 02:25:38 +0530 Subject: [PATCH 024/159] Fix Conflict with WooCommerce PDF Invoices & Packing Slips --- modules/woocommerce/woocommerce.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/woocommerce/woocommerce.php b/modules/woocommerce/woocommerce.php index 4742cc8ad4..009f9a0571 100644 --- a/modules/woocommerce/woocommerce.php +++ b/modules/woocommerce/woocommerce.php @@ -511,7 +511,7 @@ function ep_wc_setup() { add_filter( 'ep_elasticpress_enabled', 'ep_wc_blacklist_coupons', 10 ,2 ); add_filter( 'ep_indexable_post_types', 'ep_wc_post_types', 10, 1 ); add_filter( 'ep_prepare_meta_allowed_protected_keys', 'ep_wc_whitelist_meta_keys', 10, 2 ); - add_filter( 'woocommerce_shop_order_search_fields', 'ep_wc_shop_order_search_fields' ); + add_filter( 'woocommerce_shop_order_search_fields', 'ep_wc_shop_order_search_fields', 9999 ); add_filter( 'woocommerce_layered_nav_query_post_ids', 'ep_wc_convert_post_object_to_id', 10, 4 ); add_filter( 'woocommerce_unfiltered_product_ids', 'ep_wc_convert_post_object_to_id', 10, 4 ); add_filter( 'ep_sync_taxonomies', 'ep_wc_whitelist_taxonomies', 10, 2 ); From 0a318f1bf42a855139bde004416382014bec5932 Mon Sep 17 00:00:00 2001 From: Jerry Volfson Date: Tue, 1 Nov 2016 14:29:33 -0700 Subject: [PATCH 025/159] Modified approach to set default value to $ep_query instead --- classes/class-ep-wp-query-integration.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classes/class-ep-wp-query-integration.php b/classes/class-ep-wp-query-integration.php index bbc5a15417..de610100e6 100644 --- a/classes/class-ep-wp-query-integration.php +++ b/classes/class-ep-wp-query-integration.php @@ -217,6 +217,8 @@ public function filter_posts_request( $request, $query ) { $new_posts = apply_filters( 'ep_wp_query_search_cached_posts', array(), $query ); + $ep_query = array(); + if( count( $new_posts ) < 1 ) { $scope = 'current'; @@ -300,7 +302,7 @@ public function filter_posts_request( $request, $query ) { $this->posts_by_query[spl_object_hash( $query )] = $new_posts; - do_action( 'ep_wp_query_search', $new_posts, (isset($ep_query) ? $ep_query : null), $query ); + do_action( 'ep_wp_query_search', $new_posts, $ep_query, $query ); global $wpdb; From b1346012c65ac9fdae496f10230d43b7e96843d1 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Thu, 3 Nov 2016 00:17:53 +0530 Subject: [PATCH 026/159] Fix admin integration does not work when searching --- classes/class-ep-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 72653306d1..328dc4943f 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1493,7 +1493,7 @@ public function format_args( $args ) { * @since 1.3 */ - if ( ! empty( $args['s'] ) && empty( $args['ep_match_all'] ) && empty( $args['ep_integrate'] ) ) { + if ( ! empty( $args['s'] ) ) { $query['bool']['should'][2]['multi_match']['query'] = $args['s']; $query['bool']['should'][1]['multi_match']['query'] = $args['s']; $query['bool']['should'][0]['multi_match']['query'] = $args['s']; From ff773aeb51a8c4cd7e6e89ec9d5007e7e5115d75 Mon Sep 17 00:00:00 2001 From: David Naber Date: Thu, 3 Nov 2016 20:54:01 +0100 Subject: [PATCH 027/159] Trim trailing whitespace --- modules/woocommerce/woocommerce.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/woocommerce/woocommerce.php b/modules/woocommerce/woocommerce.php index 009f9a0571..f81ff208e7 100644 --- a/modules/woocommerce/woocommerce.php +++ b/modules/woocommerce/woocommerce.php @@ -347,7 +347,7 @@ function ep_wc_translate_args( $query ) { if ( ! empty( $orderby ) && 'rand' === $orderby ) { $query->set( 'orderby', false ); // Just order by relevance. } - + $s = $query->get( 's' ); if ( empty( $s ) ) { @@ -470,7 +470,7 @@ function ep_wc_remove_legacy_meta( $post_args, $post_id ) { /** * Make search coupons don't go through ES - * + * * @param bool $enabled * @param object $query * @since 2.1 @@ -525,7 +525,7 @@ function ep_wc_setup() { /** * Output module box summary - * + * * @since 2.1 */ function ep_wc_module_box_summary() { @@ -536,7 +536,7 @@ function ep_wc_module_box_summary() { /** * Output module box long - * + * * @since 2.1 */ function ep_wc_module_box_long() { From 3801a834bfd1e679dca19243d163c47772a67f92 Mon Sep 17 00:00:00 2001 From: David Naber Date: Thu, 3 Nov 2016 21:00:02 +0100 Subject: [PATCH 028/159] Integrate ES only on indexed post types #621 This fix limits integration to actuall indexed post types. If the post types aren't filtered, the behavior doesn't change at all. --- modules/woocommerce/woocommerce.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/woocommerce/woocommerce.php b/modules/woocommerce/woocommerce.php index f81ff208e7..e5dc86eadc 100644 --- a/modules/woocommerce/woocommerce.php +++ b/modules/woocommerce/woocommerce.php @@ -288,11 +288,14 @@ function ep_wc_translate_args( $query ) { /** * Force ElasticPress if product post type query */ - $supported_post_types = array( - 'product', - 'shop_order', - 'shop_order_refund', - 'product_variation' + $supported_post_types = array_intersect( + array( + 'product', + 'shop_order', + 'shop_order_refund', + 'product_variation' + ), + ep_get_indexable_post_types() ); // For orders it queries an array of shop_order and shop_order_refund post types, hence an array_diff From 96515e6ce77aec8b6bea4f91eea3c657a14f910e Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 13 Nov 2016 17:29:55 -0500 Subject: [PATCH 029/159] Version 2.2; rethink how modules are activated --- .editorconfig | 11 + assets/css/admin.css | 307 ++++++++++++-------- assets/css/admin.css.map | 2 +- assets/css/admin.min.css | 2 +- assets/css/admin.scss | 365 ++++++++++++++++-------- assets/js/admin.js | 61 ++-- assets/js/admin.min.js | 2 +- classes/class-ep-dashboard.php | 57 ++-- classes/class-ep-module.php | 156 +++++++--- classes/class-ep-modules.php | 77 +---- elasticpress.php | 30 +- includes/dashboard-page.php | 37 ++- lang/elasticpress.pot | 183 ++++++------ modules/admin/admin.php | 20 +- modules/related-posts/related-posts.php | 93 ++---- modules/related-posts/widget.php | 119 ++++++++ modules/search/search.php | 4 - modules/woocommerce/woocommerce.php | 19 +- 18 files changed, 946 insertions(+), 599 deletions(-) create mode 100644 .editorconfig create mode 100644 modules/related-posts/widget.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..a1eb91244c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 + +[*.{php,js,css,scss}] +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 4 diff --git a/assets/css/admin.css b/assets/css/admin.css index 0731013ff3..e0ce436bc0 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -1,3 +1,29 @@ +/** + * Layout + */ +.wrap .notice, +.wrap > h2 { + z-index: 2; + position: relative; } + +.wrap h2 { + color: #888; + line-height: 1.75; + margin: .5em 0 .75em; } + +#wpbody #update-nag, +#wpbody .update-nag { + margin-left: -20px; + width: 100%; + margin-top: 0; + margin-bottom: 20px; } + +h2.ep-list-modules { + display: none; } + +/** + * Header + */ .ep-header-menu { background-color: #fff; position: relative; @@ -9,14 +35,6 @@ border-bottom: 2px solid #ddd; z-index: 2; } -.progress-bar { - margin-bottom: -5px; - height: 5px; - position: absolute; - bottom: 0; - left: 0; - background-color: #D84440; } - .ep-header-menu:after { content: ' '; clear: both; @@ -66,25 +84,20 @@ vertical-align: middle; font-style: italic; } -.wrap .notice, -.wrap > h2 { - z-index: 2; - position: relative; } +.ep-header-menu .progress-bar { + margin-bottom: -5px; + height: 5px; + position: absolute; + bottom: 0; + left: 0; + background-color: #D84440; } +/** + * Modules + */ .ep-modules { overflow: auto; } -.ep-module.module-dependencies-unmet .postbox:after { - content: ' '; - display: block; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - opacity: .4; - background-color: #333; } - .error-overlay.cant-connect, .error-overlay.syncing { content: ' '; @@ -111,109 +124,163 @@ .ep-module .postbox .hndle { cursor: inherit; } -.ep-module { +.ep-module .postbox .hndle .settings-button { + float: right; + display: inline-block; + font-size: 13px; + background-color: #efefef; + border-radius: 4px; + padding: 4px 7px; + margin-top: -4px; + cursor: pointer; + color: inherit; position: relative; - vertical-align: top; - margin-bottom: 20px; } + padding-left: 23px; } -.ep-module .module-message { - text-align: center; - padding: 12px; } +.ep-module .settings-button:before { + content: "\f140"; + font: 400 19px/1 dashicons; + display: inline-block; + color: #72777c; + padding: 0 5px 0 0; + vertical-align: middle; + top: 4px; + position: absolute; + left: 1px; } -.module-message.module-error { - color: #666; - font-style: italic; } +.ep-module .settings-button:after { + border-radius: 50%; + content: ' '; + display: inline-block; + vertical-align: middle; + width: 8px; + height: 8px; + margin-left: 10px; + margin-top: -2px; } -.ep-module .deactivate { - display: none; } +.module-requirements-status-2.ep-module .postbox .hndle .settings-button:after { + background-color: transparent; + border: 1px solid #ff0000; } -.ep-module.module-active .activate, -.ep-module.module-syncing .activate, -.ep-module.module-syncing .deactivate { - display: none; } +.module-requirements-status-2.ep-module .settings .requirements-status-notice { + border-color: #ff0000; } -.ep-module.module-active.module-syncing .deactivate { - display: none; } +.module-requirements-status-1.ep-module .postbox .hndle .settings-button:after { + background-color: transparent; + border: 1px solid #e3e600; } -.ep-module .syncing-placeholder { - display: none; } +.module-requirements-status-1.ep-module.module-active .postbox .hndle .settings-button:after { + background-color: #e3e600; } -.ep-module.module-active .syncing-placeholder { - display: none; } +.module-requirements-status-1.ep-module .settings .requirements-status-notice { + border-color: #e3e600; } -.ep-module.module-syncing .syncing-placeholder { - display: inline-block; } +.module-requirements-status-0.ep-module .postbox .hndle .settings-button:after { + background-color: transparent; + border: 1px solid #6aa000; } -.ep-module.module-active .deactivate { - display: inline-block; - position: relative; } +.module-requirements-status-0.ep-module.module-active .postbox .hndle .settings-button:after { + background-color: #6aa000; } + +.module-requirements-status-0.ep-module .settings .requirements-status-notice { + border-color: #6aa000; } + +.ep-module { + position: relative; + vertical-align: top; + margin-bottom: 20px; } -.ep-module .activate.processing:after, -.ep-module .deactivate.processing:after { +.ep-module.saving .action-wrap:before { content: ' '; vertical-align: middle; - margin-left: 1.4em; - top: -.25em; + margin-right: 1.4em; + top: 4px; border-radius: 50%; - width: 1em; - height: 1em; + width: 8px; + height: 8px; display: inline-block; - font-size: 6px; + font-size: 7px; position: relative; text-indent: -9999em; - border-top: 1.1em solid rgba(255, 255, 255, 0.2); - border-right: 1.1em solid rgba(255, 255, 255, 0.2); - border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); - border-left: 1.1em solid #ffffff; + border-top: 5px solid #ccc; + border-right: 5px solid #ccc; + border-bottom: 5px solid #ccc; + border-left: 5px solid #999; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); -webkit-animation: load8 1.1s infinite linear; animation: load8 1.1s infinite linear; } -.ep-module .deactivate.processing:after { - border-top: 1.1em solid rgba(180, 180, 180, 0.4); - border-right: 1.1em solid rgba(180, 180, 180, 0.4); - border-bottom: 1.1em solid rgba(180, 180, 180, 0.4); - border-left: 1.1em solid #666; } +.ep-module .description, +.ep-module .settings { + margin-bottom: 0; + text-align: left; } -@-webkit-keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } +.ep-module .settings { + display: none; + overflow: auto; } + .ep-module .settings h3 { + margin-top: 0; + font-size: inherit; + font-weight: bold; } -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } +.ep-module.show-settings .settings { + display: block; } -.ep-module .inside { - margin-bottom: 0; - border-bottom-width: 2px; - text-align: left; } +.ep-module.show-settings .description { + display: none; } + +.ep-module.show-settings .settings-button:before { + content: "\f142"; } + +.ep-module .settings .requirements-status-notice { + border-left: 4px solid #6aa000; + background-color: #efefef; + padding: 8px 12px; + margin-bottom: 10px; } -.ep-module .action { - margin: 6px 0; - padding: 0 12px 0 13px; - text-align: right; } +.ep-module .settings .action-wrap { + clear: both; + text-align: right; + margin-top: 10px; + vertical-align: middle; } + .ep-module .settings .action-wrap a { + cursor: pointer; + display: inline-block; } + .ep-module .settings .action-wrap .cancel { + margin-top: 4px; + margin-right: 6px; } + +.ep-module .settings .field { + clear: both; + overflow: auto; } + .ep-module .settings .field > label, + .ep-module .settings .field .field-name { + display: block; + float: left; + margin-right: 3em; } + .ep-module .settings .field .input-wrap { + display: block; + float: left; } + .ep-module .settings .field .disabled { + color: #c4c4c4; } + .ep-module .settings .field .disabled input { + cursor: default; } .ep-module .long { display: none; } + .ep-module .long p:last-child { + margin-bottom: 0; } -.ep-module.show-all .long { +.ep-module.show-full .long { display: block; } .ep-module .learn-more, .ep-module .collapse { cursor: pointer; } -.ep-module.show-all .learn-more { +.ep-module.show-full .learn-more { display: none; } .ep-module .learn-more:after { @@ -232,6 +299,25 @@ top: -.07em; position: relative; } +/** + * Intro Page + */ +.intro h2 { + padding: 9px 15px 4px 0; } + +.wrap.intro { + margin-top: 30px; + margin-bottom: 30px; + overflow: auto; } + .wrap.intro .updated, + .wrap.intro .notice, + .wrap.intro .error, + .wrap.intro .is-dismissible { + display: none !important; } + +.modules-screenshot { + display: none; } + .setup-message { text-align: center; background-color: #e63e3b; @@ -253,33 +339,24 @@ background-color: #fff; color: #d84440; } -.modules-screenshot { - display: none; } - -.wrap h2 { - color: #888; - line-height: 1.75; - margin: .5em 0 .75em; } - -.intro h2 { - padding: 9px 15px 4px 0; } - -.wrap.intro { - margin-top: 30px; - margin-bottom: 30px; - overflow: auto; } - .wrap.intro .updated, - .wrap.intro .notice, - .wrap.intro .error, - .wrap.intro .is-dismissible { - display: none !important; } +/** + * Animations + */ +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } -#wpbody #update-nag, -#wpbody .update-nag { - margin-left: -20px; - width: 100%; - margin-top: 0; - margin-bottom: 20px; } +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } @media (min-width: 768px) { .intro .left { diff --git a/assets/css/admin.css.map b/assets/css/admin.css.map index 9e1ebeedc0..f554ac944d 100644 --- a/assets/css/admin.css.map +++ b/assets/css/admin.css.map @@ -1 +1 @@ -{"version":3,"sources":["admin.scss"],"names":[],"mappings":"AAEA;EACC,uBAAuB;EACvB,mBAAmB;EACnB,mBAAmB;EACnB,mBAAmB;EACnB,oBAAoB;EACpB,iBAAiB;EACjB,oBAAoB;EACpB,8BAA8B;EAC9B,WAAW,EACX;;AAED;EACC,oBAAoB;EACpB,YAAY;EACZ,mBAAmB;EACnB,UAAU;EACV,QAAQ;EACR,0BAA0B,EAC1B;;AAED;EACC,aAAa;EACb,YAAY;EACZ,eAAe,EACf;;AAED;EACC,YAAY,EACZ;;AAED;EACC,aAAa;EACb,mBAAmB;EACnB,aAAa;EACb,kBAAkB;EAClB,sBAAsB,EACtB;;AAED;EACC,gBAAgB;EAChB,mBAAmB;EACnB,UAAU,EACV;;AAED;EACC,gBAAgB;EAChB,uBAAuB;EACvB,eAAe;EACf,mBAAmB;EACnB,UAAU;EACV,iBAAiB;EACjB,gBAAgB,EAChB;;AAED;;;EAGC,SAAS;EACT,gBAAgB,EAChB;;AAED;;;EAGC,cAAc,EACd;;AAED;EACC,cAAc;EACd,YAAY;EACZ,aAAa;EACb,mBAAmB;EACnB,kBAAkB;EACf,uBAAuB;EACvB,mBAAmB,EACtB;;AAED;;EAEC,WAAW;EACR,mBAAmB,EACtB;;AAED;EACC,eAAe,EACf;;AAED;EACC,aAAa;EACb,eAAe;EACf,mBAAmB;EACnB,OAAO;EACP,QAAQ;EACR,SAAS;EACT,UAAU;EACV,YAAY;EACZ,uBAAuB,EACvB;;AAED;;EAEC,aAAa;EACb,eAAe;EACf,gBAAgB;EAChB,UAAU;EACV,QAAQ;EACR,SAAS;EACT,UAAU;EACV,YAAY;EACZ,uBAAuB;EACvB,WAAW,EACX;;AAED;EACC;;;IAGC,UAAU;IACV,YAAY,EACZ,EAAA;;AAGF;EACC,iBAAiB,EACjB;;AAED;EACC,gBAAgB,EAChB;;AAED;EACC,mBAAmB;EACnB,oBAAoB;EACpB,oBAAoB,EACpB;;AAED;EACC,mBAAmB;EACnB,cAAc,EACd;;AAED;EACC,YAAY;EACZ,mBAAmB,EACnB;;AAED;EACC,cAAc,EACd;;AAED;;;EAGC,cAAc,EACd;;AAED;EACC,cAAc,EACd;;AAED;EACC,cAAc,EACd;;AAED;EACC,cAAc,EACd;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,sBAAsB;EACtB,mBAAmB,EACnB;;AAED;;EAEC,aAAa;EACb,uBAAuB;EACvB,mBAAmB;EACnB,YAAY;EACZ,mBAAmB;EACnB,WAAW;EACX,YAAY;EACZ,sBAAsB;EACtB,eAAe;EACf,mBAAmB;EACnB,qBAAqB;EACrB,iDAA4B;EAC5B,mDAA8B;EAC9B,oDAA+B;EAC/B,iCAAiC;EACjC,iCAA6B;EAC7B,6BAAyB;EACzB,yBAAqB;EACrB,8CAA8C;EAC9C,sCAAsC,EACtC;;AAED;EACC,iDAA4B;EAC5B,mDAA8B;EAC9B,oDAA+B;EAC/B,8BAA8B,EAC9B;;AAED;EACC;IACC,gCAAyB;IACzB,wBAAiB,EAAA;EAElB;IACC,kCAAyB;IACzB,0BAAiB,EAAA,EAAA;;AAInB;EACC;IACC,gCAAyB;IACzB,wBAAiB,EAAA;EAElB;IACC,kCAAyB;IACzB,0BAAiB,EAAA,EAAA;;AAInB;EACC,iBAAiB;EACjB,yBAAyB;EACzB,iBAAiB,EACjB;;AAED;EACC,cAAc;EACd,uBAAuB;EACvB,kBAAkB,EAClB;;AAED;EACC,cAAc,EACd;;AAED;EACC,eAAe,EACf;;AAED;;EAEC,gBAAgB,EAChB;;AAED;EACC,cAAc,EACd;;AAED;EACC,iBAAiB;EACjB,uBAAuB;EACvB,iBAAiB;EACjB,uBAAuB;EACvB,YAAY;EACZ,mBAAmB,EACnB;;AAGD;EACC,iBAAiB;EACjB,uBAAuB;EACvB,iBAAiB;EACjB,uBAAuB;EACvB,YAAY;EACZ,mBAAmB,EACnB;;AAED;EACC,mBAAmB;EACnB,0BAA0B;EAC1B,2BAA2B;EAC3B,eAAe;EACf,YAAY,EACZ;;AAED;EACC,mBAAmB;EACnB,0BAA0B;EAC1B,eAAe;EACf,sBAAsB;EACtB,gBAAgB;EAChB,sBAAsB;EACtB,mBAAmB;EACnB,gDAAgC,EAChC;;AAED;EACC,uBAAuB;EACvB,eAAe,EACf;;AAED;EACC,cAAc,EACd;;AAED;EACC,YAAY;EACZ,kBAAkB;EAClB,qBAAqB,EACrB;;AAED;EACC,wBAAwB,EACxB;;AAED;EACC,iBAAiB;EACjB,oBAAoB;EACpB,eAAe,EAQf;EAXD;;;;IASE,yBAAyB,EACzB;;AAGF;;EAEC,mBAAmB;EACnB,YAAY;EACZ,cAAc;EACd,oBAAoB,EACpB;;AAED;EACC;IACC,YAAY;IACZ,WAAW,EACX;EAED;IACC,eAAe;IACf,kBAAkB;IAClB,WAAW;IACX,eAAe;IACf,aAAa;IACb,aAAa,EACb;EAED;IACC,eAAe;IACf,YAAY;IACZ,uBAAuB;IACvB,oBAAoB;IACpB,WAAW,EACX;EAED;IACC,eAAe;IACf,aAAa;IACb,uBAAuB;IACvB,mBAAmB;IACnB,WAAW,EACX;EAED;IACC,YAAY;IACZ,WAAW;IACX,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB,EAClB,EAAA","file":"admin.css"} \ No newline at end of file +{"version":3,"sources":["admin.scss"],"names":[],"mappings":"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","file":"admin.css"} \ No newline at end of file diff --git a/assets/css/admin.min.css b/assets/css/admin.min.css index a823cbda2e..957d79f299 100644 --- a/assets/css/admin.min.css +++ b/assets/css/admin.min.css @@ -1 +1 @@ -.ep-modules,.wrap.intro{overflow:auto}.ep-header-menu{background-color:#fff;position:relative;margin-left:-20px;border-bottom:2px solid #ddd;z-index:2;padding:5px 20px}.progress-bar{margin-bottom:-5px;height:5px;position:absolute;bottom:0;left:0;background-color:#D84440}.ep-header-menu:after{content:' ';clear:both;display:block}.ep-header-menu img{float:left}.ep-header-menu .icons{float:right;padding-right:3px;height:39px;line-height:39px;display:inline-block}.ep-header-menu .icons .dashicons-update{font-size:29px;margin-right:10px;top:-3px}.ep-header-menu .icons a{font-size:27px;vertical-align:middle;color:inherit;position:relative;top:-3px;margin-left:8px;cursor:pointer}.ep-header-menu .sync-status,.module-message.module-error{color:#666;font-style:italic}.ep-header-menu .icons .dashicons-controls-pause,.ep-header-menu .icons .dashicons-controls-play,.ep-header-menu .icons .dashicons-no{top:1px;font-size:30px}.ep-header-menu .cancel-sync,.ep-header-menu .pause-sync,.ep-header-menu .resume-sync{display:none}.ep-header-menu .sync-status{display:none;bottom:-1px;position:relative;margin-right:8px;vertical-align:middle}.wrap .notice,.wrap>h2{z-index:2;position:relative}.ep-module.module-dependencies-unmet .postbox:after{content:' ';display:block;position:absolute;top:0;left:0;right:0;bottom:0;opacity:.4;background-color:#333}.error-overlay.cant-connect,.error-overlay.syncing{content:' ';display:block;position:fixed;top:46px;left:0;right:0;bottom:0;opacity:.6;background-color:#fff;z-index:1}@media (min-width:768px){.error-overlay,.error-overlay.cant-connect,.error-overlay.syncing{top:32px;left:160px}}.ep-module .postbox{margin-bottom:0}.ep-module .postbox .hndle{cursor:inherit}.ep-module{position:relative;vertical-align:top;margin-bottom:20px}.ep-module .module-message{text-align:center;padding:12px}.ep-module .deactivate,.ep-module .syncing-placeholder,.ep-module.module-active .activate,.ep-module.module-active .syncing-placeholder,.ep-module.module-active.module-syncing .deactivate,.ep-module.module-syncing .activate,.ep-module.module-syncing .deactivate{display:none}.ep-module.module-syncing .syncing-placeholder{display:inline-block}.ep-module.module-active .deactivate{display:inline-block;position:relative}.ep-module .activate.processing:after,.ep-module .deactivate.processing:after{content:' ';vertical-align:middle;margin-left:1.4em;top:-.25em;border-radius:50%;width:1em;height:1em;display:inline-block;font-size:6px;position:relative;text-indent:-9999em;border-top:1.1em solid rgba(255,255,255,.2);border-right:1.1em solid rgba(255,255,255,.2);border-bottom:1.1em solid rgba(255,255,255,.2);border-left:1.1em solid #fff;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation:load8 1.1s infinite linear;animation:load8 1.1s infinite linear}.ep-module .collapse:after,.ep-module .learn-more:after{font-family:dashicons;font-size:1.5em;vertical-align:middle;top:-.07em;position:relative}.ep-module .deactivate.processing:after{border-top:1.1em solid rgba(180,180,180,.4);border-right:1.1em solid rgba(180,180,180,.4);border-bottom:1.1em solid rgba(180,180,180,.4);border-left:1.1em solid #666}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.ep-module .inside{margin-bottom:0;border-bottom-width:2px;text-align:left}.ep-module .action{margin:6px 0;padding:0 12px 0 13px;text-align:right}.ep-module .long{display:none}.ep-module.show-all .long{display:block}.ep-module .collapse,.ep-module .learn-more{cursor:pointer}.ep-module.show-all .learn-more{display:none}.ep-module .learn-more:after{content:"\f140"}.ep-module .collapse:after{content:"\f142"}.setup-message{text-align:center;background-color:#e63e3b;margin:20px -20px 0;padding:2em 0;clear:both}.setup-message .setup-button{border-radius:5px;background-color:#d73c38;color:#f19ea4;display:inline-block;margin:0 .75em;text-decoration:none;padding:.75em 3em;box-shadow:1px 1px 3px 1px rgba(0,0,0,.25)}.setup-message .setup-button-primary{background-color:#fff;color:#d84440}.modules-screenshot{display:none}.wrap h2{color:#888;line-height:1.75;margin:.5em 0 .75em}.intro h2{padding:9px 15px 4px 0}.wrap.intro{margin-top:30px;margin-bottom:30px}.wrap.intro .error,.wrap.intro .is-dismissible,.wrap.intro .notice,.wrap.intro .updated{display:none!important}#wpbody #update-nag,#wpbody .update-nag{margin-left:-20px;width:100%;margin-top:0;margin-bottom:20px}@media (min-width:768px){.intro .left{float:left;width:30%}.modules-screenshot{margin:0 auto;max-width:1000px;width:70%;display:block;height:auto;float:right}.ep-modules .left,.ep-modules .right{display:block;box-sizing:border-box;width:50%}.ep-modules .left{float:left;padding-right:10px}.ep-modules .right{float:right;padding-left:10px}.ep-module .module-message{float:left;padding:.5em 0 0;vertical-align:middle;display:inline-block}} \ No newline at end of file +.ep-header-menu,.wrap .notice,.wrap>h2{z-index:2;position:relative}.wrap h2{color:#888;line-height:1.75;margin:.5em 0 .75em}#wpbody #update-nag,#wpbody .update-nag{margin-left:-20px;width:100%;margin-top:0;margin-bottom:20px}h2.ep-list-modules{display:none}.ep-header-menu{background-color:#fff;margin-left:-20px;border-bottom:2px solid #ddd;padding:5px 20px}.ep-header-menu:after{content:' ';clear:both;display:block}.ep-header-menu img{float:left}.ep-header-menu .icons{float:right;padding-right:3px;height:39px;line-height:39px;display:inline-block}.ep-header-menu .icons .dashicons-update{font-size:29px;margin-right:10px;top:-3px}.ep-header-menu .icons a{font-size:27px;vertical-align:middle;color:inherit;position:relative;top:-3px;margin-left:8px;cursor:pointer}.ep-header-menu .icons .dashicons-controls-pause,.ep-header-menu .icons .dashicons-controls-play,.ep-header-menu .icons .dashicons-no{top:1px;font-size:30px}.ep-header-menu .cancel-sync,.ep-header-menu .pause-sync,.ep-header-menu .resume-sync{display:none}.ep-header-menu .sync-status{display:none;color:#666;bottom:-1px;position:relative;margin-right:8px;vertical-align:middle;font-style:italic}.ep-header-menu .progress-bar{margin-bottom:-5px;height:5px;position:absolute;bottom:0;left:0;background-color:#D84440}.ep-modules{overflow:auto}.error-overlay.cant-connect,.error-overlay.syncing{content:' ';display:block;position:fixed;top:46px;left:0;right:0;bottom:0;opacity:.6;background-color:#fff;z-index:1}@media (min-width:768px){.error-overlay,.error-overlay.cant-connect,.error-overlay.syncing{top:32px;left:160px}}.ep-module .postbox{margin-bottom:0}.ep-module .postbox .hndle{cursor:inherit}.ep-module .postbox .hndle .settings-button{float:right;display:inline-block;font-size:13px;background-color:#efefef;border-radius:4px;padding:4px 7px 4px 23px;margin-top:-4px;cursor:pointer;color:inherit;position:relative}.ep-module .settings-button:before{content:"\f140";font:400 19px/1 dashicons;display:inline-block;color:#72777c;padding:0 5px 0 0;vertical-align:middle;top:4px;position:absolute;left:1px}.ep-module .settings-button:after,.ep-module.saving .action-wrap:before{content:' ';width:8px;height:8px;display:inline-block}.ep-module .settings-button:after{border-radius:50%;vertical-align:middle;margin-left:10px;margin-top:-2px}.module-requirements-status-2.ep-module .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid red}.module-requirements-status-2.ep-module .settings .requirements-status-notice{border-color:red}.module-requirements-status-1.ep-module .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid #e3e600}.module-requirements-status-1.ep-module.module-active .postbox .hndle .settings-button:after{background-color:#e3e600}.module-requirements-status-1.ep-module .settings .requirements-status-notice{border-color:#e3e600}.module-requirements-status-0.ep-module .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid #6aa000}.module-requirements-status-0.ep-module.module-active .postbox .hndle .settings-button:after{background-color:#6aa000}.module-requirements-status-0.ep-module .settings .requirements-status-notice{border-color:#6aa000}.ep-module{position:relative;vertical-align:top;margin-bottom:20px}.ep-module.saving .action-wrap:before{vertical-align:middle;margin-right:1.4em;top:4px;border-radius:50%;font-size:7px;position:relative;text-indent:-9999em;border-top:5px solid #ccc;border-right:5px solid #ccc;border-bottom:5px solid #ccc;border-left:5px solid #999;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation:load8 1.1s infinite linear;animation:load8 1.1s infinite linear}.ep-module .description,.ep-module .settings{margin-bottom:0;text-align:left}.ep-module .settings{display:none;overflow:auto}.ep-module .settings h3{margin-top:0;font-size:inherit;font-weight:700}.ep-module .collapse:after,.ep-module .learn-more:after{font-family:dashicons;font-size:1.5em;vertical-align:middle;top:-.07em;position:relative}.ep-module.show-settings .settings{display:block}.ep-module.show-settings .description{display:none}.ep-module.show-settings .settings-button:before{content:"\f142"}.ep-module .settings .requirements-status-notice{border-left:4px solid #6aa000;background-color:#efefef;padding:8px 12px;margin-bottom:10px}.ep-module .settings .action-wrap{clear:both;text-align:right;margin-top:10px;vertical-align:middle}.ep-module .settings .action-wrap a{cursor:pointer;display:inline-block}.ep-module .settings .action-wrap .cancel{margin-top:4px;margin-right:6px}.ep-module .settings .field{clear:both;overflow:auto}.ep-module .settings .field .field-name,.ep-module .settings .field>label{display:block;float:left;margin-right:3em}.ep-module .settings .field .input-wrap{display:block;float:left}.ep-module .settings .field .disabled{color:#c4c4c4}.ep-module .settings .field .disabled input{cursor:default}.ep-module .long{display:none}.ep-module .long p:last-child{margin-bottom:0}.ep-module.show-full .long{display:block}.ep-module .collapse,.ep-module .learn-more{cursor:pointer}.ep-module.show-full .learn-more{display:none}.ep-module .learn-more:after{content:"\f140"}.ep-module .collapse:after{content:"\f142"}.intro h2{padding:9px 15px 4px 0}.wrap.intro{margin-top:30px;margin-bottom:30px;overflow:auto}.wrap.intro .error,.wrap.intro .is-dismissible,.wrap.intro .notice,.wrap.intro .updated{display:none!important}.modules-screenshot{display:none}.setup-message{text-align:center;background-color:#e63e3b;margin:20px -20px 0;padding:2em 0;clear:both}.setup-message .setup-button{border-radius:5px;background-color:#d73c38;color:#f19ea4;display:inline-block;margin:0 .75em;text-decoration:none;padding:.75em 3em;box-shadow:1px 1px 3px 1px rgba(0,0,0,.25)}.setup-message .setup-button-primary{background-color:#fff;color:#d84440}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media (min-width:768px){.intro .left{float:left;width:30%}.modules-screenshot{margin:0 auto;max-width:1000px;width:70%;display:block;height:auto;float:right}.ep-modules .left,.ep-modules .right{display:block;box-sizing:border-box;width:50%}.ep-modules .left{float:left;padding-right:10px}.ep-modules .right{float:right;padding-left:10px}.ep-module .module-message{float:left;padding:.5em 0 0;vertical-align:middle;display:inline-block}} \ No newline at end of file diff --git a/assets/css/admin.scss b/assets/css/admin.scss index 275bd8aa69..d92098cbc9 100644 --- a/assets/css/admin.scss +++ b/assets/css/admin.scss @@ -1,4 +1,39 @@ $tablet-width: 768px; +$status-ok: #6aa000; +$status-warning: #e3e600; +$status-error: #ff0000; + +/** + * Layout + */ + +.wrap .notice, +.wrap > h2 { + z-index: 2; + position: relative; +} + +.wrap h2 { + color: #888; + line-height: 1.75; + margin: .5em 0 .75em; +} + +#wpbody #update-nag, +#wpbody .update-nag { + margin-left: -20px; + width: 100%; + margin-top: 0; + margin-bottom: 20px; +} + +h2.ep-list-modules { + display: none; // We use this since WP inserts warnings after the first h2 +} + +/** + * Header + */ .ep-header-menu { background-color: #fff; @@ -12,15 +47,6 @@ $tablet-width: 768px; z-index: 2; } -.progress-bar { - margin-bottom: -5px; - height: 5px; - position: absolute; - bottom: 0; - left: 0; - background-color: #D84440; -} - .ep-header-menu:after { content: ' '; clear: both; @@ -78,28 +104,23 @@ $tablet-width: 768px; font-style: italic; } -.wrap .notice, -.wrap > h2 { - z-index: 2; - position: relative; +.ep-header-menu .progress-bar { + margin-bottom: -5px; + height: 5px; + position: absolute; + bottom: 0; + left: 0; + background-color: #D84440; } +/** + * Modules + */ + .ep-modules { overflow: auto; } -.ep-module.module-dependencies-unmet .postbox:after { - content: ' '; - display: block; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - opacity: .4; - background-color: #333; -} - .error-overlay.cant-connect, .error-overlay.syncing { content: ' '; @@ -131,70 +152,106 @@ $tablet-width: 768px; cursor: inherit; } -.ep-module { +.ep-module .postbox .hndle .settings-button { + float: right; + display: inline-block; + font-size: 13px; + background-color: #efefef; + border-radius: 4px; + padding: 4px 7px; + margin-top: -4px; + cursor: pointer; + color: inherit; position: relative; - vertical-align: top; - margin-bottom: 20px; + padding-left: 23px; } -.ep-module .module-message { - text-align: center; - padding: 12px; +.ep-module .settings-button:before { + content: "\f140"; + font: 400 19px/1 dashicons; + display: inline-block; + color: #72777c; + padding: 0 5px 0 0; + vertical-align: middle; + top: 4px; + position: absolute; + left: 1px; } -.module-message.module-error { - color: #666; - font-style: italic; +.ep-module .settings-button:after { + border-radius: 50%; + content: ' '; + display: inline-block; + vertical-align: middle; + width: 8px; + height: 8px; + margin-left: 10px; + margin-top: -2px; } -.ep-module .deactivate { - display: none; -} +.module-requirements-status-2.ep-module { + .postbox .hndle .settings-button:after { + background-color: transparent; + border: 1px solid $status-error; + } -.ep-module.module-active .activate, -.ep-module.module-syncing .activate, -.ep-module.module-syncing .deactivate { - display: none; + .settings .requirements-status-notice { + border-color: $status-error; + } } -.ep-module.module-active.module-syncing .deactivate { - display: none; -} +.module-requirements-status-1.ep-module { + .postbox .hndle .settings-button:after { + background-color: transparent; + border: 1px solid $status-warning; + } -.ep-module .syncing-placeholder { - display: none; -} + &.module-active .postbox .hndle .settings-button:after { + background-color: $status-warning; + } -.ep-module.module-active .syncing-placeholder { - display: none; + .settings .requirements-status-notice { + border-color: $status-warning; + } } -.ep-module.module-syncing .syncing-placeholder { - display: inline-block; +.module-requirements-status-0.ep-module { + .postbox .hndle .settings-button:after { + background-color: transparent; + border: 1px solid $status-ok; + } + + &.module-active .postbox .hndle .settings-button:after { + background-color: $status-ok; + } + + .settings .requirements-status-notice { + border-color: $status-ok; + } } -.ep-module.module-active .deactivate { - display: inline-block; +.ep-module { position: relative; + vertical-align: top; + margin-bottom: 20px; } -.ep-module .activate.processing:after, -.ep-module .deactivate.processing:after { +.ep-module.saving .action-wrap:before { content: ' '; vertical-align: middle; - margin-left: 1.4em; - top: -.25em; + margin-right: 1.4em; + top: 4px; border-radius: 50%; - width: 1em; - height: 1em; + width: 8px; + height: 8px; display: inline-block; - font-size: 6px; + font-size: 7px; position: relative; text-indent: -9999em; - border-top: 1.1em solid rgba(255, 255, 255, 0.2); - border-right: 1.1em solid rgba(255, 255, 255, 0.2); - border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); - border-left: 1.1em solid #ffffff; + border-top: 5px solid #ccc; + border-right: 5px solid #ccc; + border-bottom: 5px solid #ccc; + border-left: 5px solid #999; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); @@ -202,52 +259,96 @@ $tablet-width: 768px; animation: load8 1.1s infinite linear; } -.ep-module .deactivate.processing:after { - border-top: 1.1em solid rgba(180, 180, 180, 0.4); - border-right: 1.1em solid rgba(180, 180, 180, 0.4); - border-bottom: 1.1em solid rgba(180, 180, 180, 0.4); - border-left: 1.1em solid #666; +.ep-module .description, +.ep-module .settings { + margin-bottom: 0; + text-align: left; } -@-webkit-keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); +.ep-module .settings { + display: none; + overflow: auto; + + h3 { + margin-top: 0; + font-size: inherit; + font-weight: bold; } } -@keyframes load8 { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); +.ep-module.show-settings { + + .settings { + display: block; } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); + + .description { + display: none; + } + + .settings-button:before { + content: "\f142"; } } -.ep-module .inside { - margin-bottom: 0; - border-bottom-width: 2px; - text-align: left; +.ep-module .settings .requirements-status-notice { + border-left: 4px solid $status-ok; + background-color: #efefef; + padding: 8px 12px; + margin-bottom: 10px; } -.ep-module .action { - margin: 6px 0; - padding: 0 12px 0 13px; +.ep-module .settings .action-wrap { + clear: both; text-align: right; + margin-top: 10px; + vertical-align: middle; + + a { + cursor: pointer; + display: inline-block; + } + + .cancel { + margin-top: 4px; + margin-right: 6px; + } +} + +.ep-module .settings .field { + clear: both; + overflow: auto; + + > label, + .field-name { + display: block; + float: left; + margin-right: 3em; + } + + .input-wrap { + display: block; + float: left; + } + + .disabled { + color: lighten(#444, 50%); + + input { + cursor: default; + } + } } .ep-module .long { display: none; + + p:last-child { + margin-bottom: 0; + } } -.ep-module.show-all .long { +.ep-module.show-full .long { display: block; } @@ -256,7 +357,7 @@ $tablet-width: 768px; cursor: pointer; } -.ep-module.show-all .learn-more { +.ep-module.show-full .learn-more { display: none; } @@ -269,7 +370,6 @@ $tablet-width: 768px; position: relative; } - .ep-module .collapse:after { content: "\f142"; font-family: dashicons; @@ -279,6 +379,32 @@ $tablet-width: 768px; position: relative; } +/** + * Intro Page + */ + + +.intro h2 { + padding: 9px 15px 4px 0; +} + +.wrap.intro { + margin-top: 30px; + margin-bottom: 30px; + overflow: auto; + + .updated, + .notice, + .error, + .is-dismissible { + display: none !important; + } +} + +.modules-screenshot { + display: none; +} + .setup-message { text-align: center; background-color: #e63e3b; @@ -303,39 +429,30 @@ $tablet-width: 768px; color: #d84440; } -.modules-screenshot { - display: none; -} +/** + * Animations + */ -.wrap h2 { - color: #888; - line-height: 1.75; - margin: .5em 0 .75em; -} - -.intro h2 { - padding: 9px 15px 4px 0; -} - -.wrap.intro { - margin-top: 30px; - margin-bottom: 30px; - overflow: auto; - - .updated, - .notice, - .error, - .is-dismissible { - display: none !important; +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } -#wpbody #update-nag, -#wpbody .update-nag { - margin-left: -20px; - width: 100%; - margin-top: 0; - margin-bottom: 20px; +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } } @media (min-width: $tablet-width) { @@ -376,4 +493,4 @@ $tablet-width: 768px; display: inline-block; padding-top: .5em; } -} \ No newline at end of file +} diff --git a/assets/js/admin.js b/assets/js/admin.js index 58883ed84f..2b2c6a78ca 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -1,13 +1,13 @@ ( function( $ ) { var $modules = $( document.getElementsByClassName( 'ep-modules' ) ); - var $errorOverlay = $( '.error-overlay' ); + var $errorOverlay = $( document.getElementsByClassName( 'error-overlay' ) ); - var $progressBar = $( '.progress-bar' ); - var $syncStatusText = $( '.sync-status' ); - var $startSyncButton = $( '.start-sync' ); - var $resumeSyncButton = $( '.resume-sync' ); - var $pauseSyncButton = $( '.pause-sync' ); - var $cancelSyncButton = $( '.cancel-sync' ); + var $progressBar = $(document.getElementsByClassName( 'progress-bar' ) ); + var $syncStatusText = $(document.getElementsByClassName( 'sync-status' ) ); + var $startSyncButton = $(document.getElementsByClassName( 'start-sync' ) ); + var $resumeSyncButton = $(document.getElementsByClassName( 'resume-sync' ) ); + var $pauseSyncButton = $(document.getElementsByClassName( 'pause-sync' ) ); + var $cancelSyncButton = $(document.getElementsByClassName( 'cancel-sync' ) ); var syncStatus = 'sync'; var moduleSync = false; @@ -18,35 +18,58 @@ $modules.on( 'click', '.learn-more, .collapse', function( event ) { $module = $( this ).parents( '.ep-module' ); - $module.toggleClass( 'show-all' ); + $module.toggleClass( 'show-full' ); } ); - $modules.on( 'click', '.js-toggle-module', function( event ) { + $modules.on( 'click', '.settings-button', function( event ) { + $module = $( this ).parents( '.ep-module' ); + $module.toggleClass( 'show-settings' ); + } ); + + $modules.on( 'click', '.save-settings', function( event ) { event.preventDefault(); var module = event.target.getAttribute( 'data-module' ); - - var $button = $( this ); - $button.addClass( 'processing' ); var $module = $modules.find( '.ep-module-' + module ); + var settings = {}; + + var $settings = $module.find('.setting-field'); + + $settings.each(function() { + var type = $( this ).attr( 'type' ); + var name = $( this ).attr( 'data-field-name' ); + var value = $( this ).attr( 'value' ); + + if ( 'radio' === type ) { + if ( $( this ).attr( 'checked' ) ) { + settings[ name ] = value; + } + } + }); + + $module.addClass( 'saving' ); + $.ajax( { method: 'post', url: ajaxurl, data: { - action: 'ep_toggle_module', + action: 'ep_save_module', module: module, - nonce: ep.nonce + nonce: ep.nonce, + settings: settings } } ).done( function( response ) { setTimeout( function() { - $button.removeClass( 'processing' ); + $module.removeClass( 'saving' ); - if( ! response.data.active_error ) { - $module.toggleClass( 'module-active' ); + if ( '1' === settings.active ) { + $module.addClass( 'module-active' ); + } else { + $module.removeClass( 'module-active' ); } - if ( response.data.active && response.data.reindex ) { + if ( response.data.reindex ) { syncStatus = 'sync'; $module.addClass( 'module-syncing' ); @@ -58,7 +81,7 @@ }, 700 ); } ).error( function() { setTimeout( function() { - $button.removeClass( 'processing' ); + $module.removeClass( 'saving' ); $module.removeClass( 'module-active' ); $module.removeClass( 'module-syncing' ); }, 700 ); diff --git a/assets/js/admin.min.js b/assets/js/admin.min.js index 60480f2e37..875cc35baa 100644 --- a/assets/js/admin.min.js +++ b/assets/js/admin.min.js @@ -1 +1 @@ -!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=ep.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=ep.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=ep.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(ep.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null}else if("finished"===o){if(j.text(ep.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:ep.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",module_sync:p,nonce:ep.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-modules")),h=a(".error-overlay"),i=a(".progress-bar"),j=a(".sync-status"),k=a(".start-sync"),l=a(".resume-sync"),m=a(".pause-sync"),n=a(".cancel-sync"),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$module=a(this).parents(".ep-module"),$module.toggleClass("show-all")}),g.on("click",".js-toggle-module",function(b){b.preventDefault();var c=b.target.getAttribute("data-module"),e=a(this);e.addClass("processing");var f=g.find(".ep-module-"+c);a.ajax({method:"post",url:ajaxurl,data:{action:"ep_toggle_module",module:c,nonce:ep.nonce}}).done(function(a){setTimeout(function(){e.removeClass("processing"),a.data.active_error||f.toggleClass("module-active"),a.data.active&&a.data.reindex&&(o="sync",f.addClass("module-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("processing"),f.removeClass("module-active"),f.removeClass("module-syncing")},700)})}),ep.index_meta&&(ep.index_meta.wpcli?(o="wpcli",b()):(q=ep.index_meta.offset,r=ep.index_meta.found_posts,ep.index_meta.module_sync&&(p=ep.index_meta.module_sync),ep.index_meta.current_site&&(e=ep.index_meta.current_site),ep.index_meta.site_stack&&(f=ep.index_meta.site_stack),f&&f.length?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):0!==r||ep.index_meta.start?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):(o="finished",b()))),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file +!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=ep.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=ep.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=ep.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(ep.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null}else if("finished"===o){if(j.text(ep.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:ep.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",module_sync:p,nonce:ep.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-modules")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$module=a(this).parents(".ep-module"),$module.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$module=a(this).parents(".ep-module"),$module.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-module"),e=g.find(".ep-module-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_module",module:c,nonce:ep.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("module-active"):e.removeClass("module-active"),a.data.reindex&&(o="sync",e.addClass("module-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("module-active"),e.removeClass("module-syncing")},700)})}),ep.index_meta&&(ep.index_meta.wpcli?(o="wpcli",b()):(q=ep.index_meta.offset,r=ep.index_meta.found_posts,ep.index_meta.module_sync&&(p=ep.index_meta.module_sync),ep.index_meta.current_site&&(e=ep.index_meta.current_site),ep.index_meta.site_stack&&(f=ep.index_meta.site_stack),f&&f.length?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):0!==r||ep.index_meta.start?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):(o="finished",b()))),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index dedd81ff6d..27239d50b6 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -36,7 +36,7 @@ public function setup() { add_action( 'admin_menu', array( $this, 'action_admin_menu' ) ); } - add_action( 'wp_ajax_ep_toggle_module', array( $this, 'action_wp_ajax_ep_toggle_module' ) ); + add_action( 'wp_ajax_ep_save_module', array( $this, 'action_wp_ajax_ep_save_module' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); add_action( 'admin_init', array( $this, 'action_admin_init' ) ); add_action( 'admin_init', array( $this, 'intro_or_dashboard' ) ); @@ -379,59 +379,42 @@ public function action_wp_ajax_ep_cancel_index() { } /** - * Toggle module active or inactive + * Save individual module settings * - * @since 2.1 + * @since 2.2 */ - public function action_wp_ajax_ep_toggle_module() { - if ( empty( $_POST['module'] ) || ! check_ajax_referer( 'ep_nonce', 'nonce', false ) ) { + public function action_wp_ajax_ep_save_module() { + if ( empty( $_POST['module'] ) || empty( $_POST['settings'] ) || ! check_ajax_referer( 'ep_nonce', 'nonce', false ) ) { wp_send_json_error(); exit; } $module = ep_get_registered_module( $_POST['module'] ); + $original_state = $module->is_active(); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $active_modules = get_site_option( 'ep_active_modules', array() ); + $module_settings = get_site_option( 'ep_module_settings', array() ); } else { - $active_modules = get_option( 'ep_active_modules', array() ); + $module_settings = get_option( 'ep_module_settings', array() ); } - $data = array(); - - if ( $module->is_active() - || ( ! $module->is_active() && is_wp_error( $module->dependencies_met() ) ) - ) { - $key = array_search( $_POST['module'], $active_modules ); + $module_settings[ $_POST['module'] ] = wp_parse_args( $_POST['settings'], $module->default_settings ); + $module_settings[ $_POST['module'] ]['active'] = (bool) $module_settings[ $_POST['module'] ]['active']; - if ( false !== $key ) { - unset( $active_modules[$key] ); - } + $sanitize_module_settings = apply_filters( 'ep_sanitize_module_settings', $module_settings, $module ); - $data['active'] = false; - $data['active_error'] = false; + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_module_settings', $sanitize_module_settings ); } else { - $active_modules[] = $module->slug; - - if ( $module->requires_install_reindex ) { - $data['reindex'] = true; - } - - $module->post_activation(); - - $data['active'] = true; - $data['active_error'] = false; - } - - // If try to activate the module but it doesn't meet dependency requirement - if( ! $module->is_active() && is_wp_error( $module->dependencies_met() ) ){ - $data['active_error'] = true; + update_option( 'ep_module_settings', $sanitize_module_settings ); } - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_active_modules', $active_modules ); - } else { - update_option( 'ep_active_modules', $active_modules ); + $data = array( + 'reindex' => false, + ); + + if ( $module_settings[ $_POST['module'] ]['active'] && ! $original_state ) { + $data['reindex'] = true; } wp_send_json_success( $data ); diff --git a/classes/class-ep-module.php b/classes/class-ep-module.php index 36749e8597..9e250abc2e 100644 --- a/classes/class-ep-module.php +++ b/classes/class-ep-module.php @@ -11,6 +11,45 @@ exit; // Exit if accessed directly. } +/** + * Just an easy way to represent a module requirements status + */ +class EP_Module_Requirements_Status { + + /** + * Initialize class + * + * @param int $code + * @param string $message + * @since 2.2 + */ + public function __construct( $code, $message = null ) { + $this->code = $code; + + $this->message = $message; + } + + /** + * Returns the status of a module + * + * 1 is no issues (hollow green) + * 2 is usable but there are warnngs (hollow yellow) + * 3 is not usable (hollow red) + * + * @var int + * @since 2.2 + */ + public $code; + + /** + * Optional message to describe status code + * + * @var string + * @since 2.2 + */ + public $message; +} + class EP_Module { /** * Module slug @@ -28,6 +67,14 @@ class EP_Module { */ public $title; + /** + * Optional module default settings + * + * @since 2.2 + * @var array + */ + public $default_settings = array(); + /** * Contains registered callback to execute after setup * @@ -53,12 +100,12 @@ class EP_Module { public $module_box_long_cb; /** - * Contains registered callback to determine if a modules dependencies are met - * - * @since 2.1 + * Output optional extra settings fields + * + * @since 2.2 * @var callback */ - public $dependencies_met_cb; + public $module_box_settings_cb; /** * Contains registered callback to execute after activation @@ -76,14 +123,6 @@ class EP_Module { */ public $requires_install_reindex; - /** - * True if the module is active - * - * @since 2.1 - * @var boolean - */ - public $active; - /** * Initiate the module, setting all relevant instance variables * @@ -98,35 +137,54 @@ public function __construct( $args ) { } /** - * Ran after a function is activated + * Run on every page load for module to set itself up * * @since 2.1 */ public function setup() { if ( ! empty( $this->setup_cb ) ) { - call_user_func( $this->setup_cb ); + call_user_func( $this->setup_cb, $this ); } - $this->active = true; - do_action( 'ep_module_setup', $this->slug, $this ); } /** - * Ran to see if dependencies are met. Returns true or a WP_Error containing an error message - * to display + * Returns requirements status of module * - * @since 2.1 - * @return bool|WP_Error + * @since 2.2 + * @return EP_Module_Requirements_Status */ - public function dependencies_met() { - $ret = true; + public function requirements_status() { + $status = new EP_Module_Requirements_Status( 0 ); + + if ( ! empty( $this->requirements_status_cb ) ) { + $status = call_user_func( $this->requirements_status_cb, $status, $this ); + } + + return apply_filters( 'ep_module_requirements_status', $status, $this ); + } + + /** + * Returns true if module is active + * + * @since 2.2 + * @return boolean + */ + public function is_active() { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $module_settings = get_site_option( 'ep_module_settings', array() ); + } else { + $module_settings = get_option( 'ep_module_settings', array() ); + } + + $active = false; - if ( ! empty( $this->dependencies_met_cb ) ) { - $ret = apply_filters( 'ep_module_dependencies_met', call_user_func( $this->dependencies_met_cb ) ); + if ( ! empty( $module_settings[ $this->slug ] ) && $module_settings[ $this->slug ]['active'] ) { + $active = true; } - return $ret; + return apply_filters( 'ep_module_active', $active, $module_settings, $this ); } /** @@ -136,7 +194,7 @@ public function dependencies_met() { */ public function post_activation() { if ( ! empty( $this->post_activation_cb ) ) { - call_user_func( $this->post_activation_cb ); + call_user_func( $this->post_activation_cb, $this ); } do_action( 'ep_module_post_activation', $this->slug, $this ); @@ -149,7 +207,7 @@ public function post_activation() { */ public function output_module_box() { if ( ! empty( $this->module_box_summary_cb ) ) { - call_user_func( $this->module_box_summary_cb ); + call_user_func( $this->module_box_summary_cb, $this ); } do_action( 'ep_module_box_summary', $this->slug, $this ); @@ -160,7 +218,7 @@ public function output_module_box() {
- module_box_long_cb ); ?> + module_box_long_cb, $this ); ?>

slug, $this ); ?> @@ -177,19 +235,43 @@ public function output_module_box() { */ public function output_module_box_full() { if ( ! empty( $this->module_box_full_cb ) ) { - call_user_func( $this->module_box_full_cb ); + call_user_func( $this->module_box_full_cb, $this ); } do_action( 'ep_module_box_full', $this->slug, $this ); } - /** - * Returns true if the module is active - * - * @since 2.1 - * @return boolean - */ - public function is_active() { - return ( ! empty( $this->active ) ); + public function output_settings_box() { + $requirements_status = $this->requirements_status(); + ?> + + message ) ) : ?> +
+ message ); ?> +
+ + +

+ +
+
+
+
+ +
+
+ + module_box_settings_cb ) ) { + call_user_func( $this->module_box_settings_cb, $this ); + return; + } + do_action( 'ep_module_box_settings', $this->slug, $this ); + ?> + +
+ +
+ registered_modules as $module ) { - if ( $module->active ) { - $active[$module->slug] = $module; - } - } - - return $active; - } - - /** - * Set up all active modules that are stored in options + * Set up all active modules * * @since 2.1 */ public function setup_modules() { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $modules = get_site_option( 'ep_active_modules', array() ); - } else { - $modules = get_option( 'ep_active_modules', array() ); - } - $modules = apply_filters( 'ep_active_modules', $modules ); - - foreach ( $modules as $module_slug ) { - if ( empty( $this->registered_modules[$module_slug] ) ) { - continue; + foreach ( $this->registered_modules as $module_slug => $module ) { + if ( $module->is_active() ) { + $module->setup(); } - - $this->registered_modules[$module_slug]->setup(); } } @@ -154,10 +109,6 @@ function ep_register_module( $slug, $module_args ) { return EP_Modules::factory()->register_module( $slug, $module_args ); } -function ep_activate_module( $slug ) { - return EP_Modules::factory()->activate_module( $slug ); -} - /** * Easy access function to get a EP_Module object from a slug * @param string $slug diff --git a/elasticpress.php b/elasticpress.php index 0a340f0d8d..3c3380cf32 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -56,23 +56,35 @@ } /** - * If we activate the plugin with no modules option, activate search by default. This - * should only happy when first upgrading to 2.1. We also want to clear any syncs that were - * in progress when the plugin was deactivated. + * On activate, all modules that meet their requirements with no warnings should be activated. * * @since 2.1 */ function ep_on_activate() { - $active_modules = get_option( 'ep_active_modules', false ); - - if ( false === $active_modules ) { - $active_modules = array( 'search' ); + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $module_settings = get_site_option( 'ep_module_settings', false ); + } else { + $module_settings = get_option( 'ep_module_settings', false ); } + + if ( false === $module_settings ) { + $registered_modules = EP_Modules::factory()->registered_modules; + + foreach ( $registered_modules as $slug => $module ) { + if ( 0 === $module->requirements_status()->code ) { + $module_settings[ $slug ] = ( ! empty( $module->default_settings ) ) ? $module->default_settings : array(); + $module_settings[ $slug ]['active'] = true; + + $module->post_activation(); + } + } + } + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_active_modules', $active_modules ); + update_site_option( 'ep_module_settings', $module_settings ); delete_site_option( 'ep_index_meta' ); } else { - update_option( 'ep_active_modules', $active_modules ); + update_option( 'ep_module_settings', $module_settings ); delete_option( 'ep_index_meta' ); } } diff --git a/includes/dashboard-page.php b/includes/dashboard-page.php index 4ce4dcc093..e972935a77 100644 --- a/includes/dashboard-page.php +++ b/includes/dashboard-page.php @@ -21,10 +21,7 @@
-

- -

- +

registered_modules; ?> @@ -34,38 +31,36 @@ $i = 0; foreach ( $modules as $module ) : $i++; - $module_classes = ( $module->is_active() ) ? 'module-active' : ''; + $requirements_status = $module->requirements_status(); + $active = $module->is_active(); + + $module_classes = 'module-requirements-status-' . (int) $requirements_status->code; + + if ( ! empty( $active ) ) { + $module_classes .= ' module-active'; + } if ( ! empty( $index_meta ) && ! empty( $index_meta['module_sync'] ) && $module->slug === $index_meta['module_sync'] ) { $module_classes .= ' module-syncing'; } - $deps_met = $module->dependencies_met(); - if ( is_wp_error( $deps_met ) ) { - $module_classes .= ' module-dependencies-unmet'; - } ob_start(); ?>
-

title ); ?>

+

+ title ); ?> + +

-
+
output_module_box(); ?>
-
-
- - get_error_message() ); ?> - -
- - - - +
+ output_settings_box(); ?>
diff --git a/lang/elasticpress.pot b/lang/elasticpress.pot index 5db6a685c7..47c4b87d5e 100644 --- a/lang/elasticpress.pot +++ b/lang/elasticpress.pot @@ -2,9 +2,9 @@ # This file is distributed under the GPLv2 or later. msgid "" msgstr "" -"Project-Id-Version: ElasticPress 2.1\n" +"Project-Id-Version: ElasticPress 2.1.1\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/elasticpress\n" -"POT-Creation-Date: 2016-09-20 20:13:21+00:00\n" +"POT-Creation-Date: 2016-11-13 22:19:22+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -127,37 +127,37 @@ msgid "" "\r\n" msgstr "" -#: bin/wp-cli.php:800 +#: bin/wp-cli.php:802 msgid "" "There is no Elasticsearch host set up. Either add one through the dashboard " "or define one in wp-config.php" msgstr "" -#: bin/wp-cli.php:802 +#: bin/wp-cli.php:804 msgid "Unable to reach Elasticsearch Server! Check that service is running." msgstr "" -#: classes/class-ep-api.php:1926 +#: classes/class-ep-api.php:1948 msgid "" "Invalid response from ElasticPress server. Please contact your " "administrator." msgstr "" -#: classes/class-ep-api.php:1939 +#: classes/class-ep-api.php:1961 msgid "" "Site not indexed.

Please run: wp elasticpress index --setup " "--network-wide using WP-CLI. Or use the index button on the left of " "this screen.

" msgstr "" -#: classes/class-ep-api.php:1943 +#: classes/class-ep-api.php:1965 msgid "" "Site not indexed.

Please run: wp elasticpress index --setup " "using WP-CLI. Or use the index button on the left of this screen.

" msgstr "" -#: classes/class-ep-api.php:1981 classes/class-ep-api.php:2040 -#: classes/class-ep-api.php:2080 classes/class-ep-api.php:2131 +#: classes/class-ep-api.php:2003 classes/class-ep-api.php:2062 +#: classes/class-ep-api.php:2102 classes/class-ep-api.php:2153 msgid "Elasticsearch Host is not available." msgstr "" @@ -177,32 +177,32 @@ msgid "" "need to fix it for ElasticPress to work." msgstr "" -#: classes/class-ep-dashboard.php:474 +#: classes/class-ep-dashboard.php:457 msgid "Sync complete" msgstr "" -#: classes/class-ep-dashboard.php:475 +#: classes/class-ep-dashboard.php:458 msgid "Sync paused" msgstr "" -#: classes/class-ep-dashboard.php:476 +#: classes/class-ep-dashboard.php:459 msgid "Syncing" msgstr "" -#: classes/class-ep-dashboard.php:477 +#: classes/class-ep-dashboard.php:460 msgid "WP CLI sync is occuring. Refresh the page to see if it's finished" msgstr "" -#: classes/class-ep-dashboard.php:478 +#: classes/class-ep-dashboard.php:461 msgid "An error occured while syncing" msgstr "" -#: classes/class-ep-dashboard.php:498 +#: classes/class-ep-dashboard.php:481 msgid "Security error!" msgstr "" -#: classes/class-ep-dashboard.php:607 classes/class-ep-dashboard.php:608 -#: includes/settings-page.php:27 +#: classes/class-ep-dashboard.php:590 classes/class-ep-dashboard.php:591 +#: classes/class-ep-module.php:254 includes/settings-page.php:27 #: vendor/woocommerce/includes/admin/class-wc-admin-menus.php:80 #: vendor/woocommerce/includes/admin/settings/class-wc-settings-api.php:45 #: vendor/woocommerce/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php:52 @@ -211,20 +211,54 @@ msgstr "" msgid "Settings" msgstr "" -#: classes/class-ep-dashboard.php:616 classes/class-ep-dashboard.php:617 +#: classes/class-ep-dashboard.php:599 classes/class-ep-dashboard.php:600 msgid "Welcome" msgstr "" -#: classes/class-ep-module.php:160 +#: classes/class-ep-module.php:218 #: vendor/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-product-data.php:616 #: vendor/woocommerce/includes/gateways/simplify-commerce/class-wc-gateway-simplify-commerce.php:98 msgid "Learn more" msgstr "" -#: classes/class-ep-module.php:165 +#: classes/class-ep-module.php:223 msgid "Collapse" msgstr "" +#: classes/class-ep-module.php:257 +#: vendor/woocommerce/includes/admin/class-wc-admin-post-types.php:272 +#: vendor/woocommerce/includes/admin/class-wc-admin-webhooks-table-list.php:41 +#: vendor/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-product-data.php:671 +#: vendor/woocommerce/includes/admin/settings/views/html-webhook-log.php:25 +#: vendor/woocommerce/includes/admin/settings/views/html-webhooks-edit.php:24 +#: vendor/woocommerce/includes/wc-account-functions.php:178 +#: vendor/woocommerce/templates/myaccount/my-orders.php:15 +msgid "Status" +msgstr "" + +#: classes/class-ep-module.php:259 +#: vendor/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php:76 +#: vendor/woocommerce/includes/admin/settings/class-wc-settings-checkout.php:292 +#: vendor/woocommerce/includes/admin/settings/class-wc-settings-emails.php:269 +#: vendor/woocommerce/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php:20 +#: vendor/woocommerce/includes/admin/views/html-admin-page-status-tools.php:31 +#: vendor/woocommerce/includes/admin/views/html-admin-page-status-tools.php:42 +#: vendor/woocommerce/includes/admin/views/html-admin-page-status-tools.php:53 +msgid "Enabled" +msgstr "" + +#: classes/class-ep-module.php:260 +#: vendor/woocommerce/includes/admin/settings/class-wc-settings-emails.php:271 +#: vendor/woocommerce/includes/wc-webhook-functions.php:25 +msgid "Disabled" +msgstr "" + +#: classes/class-ep-module.php:273 +#: vendor/woocommerce/includes/admin/meta-boxes/views/html-order-items.php:246 +#: vendor/woocommerce/templates/myaccount/form-reset-password.php:47 +msgid "Save" +msgstr "" + #: classes/class-ep-wp-date-query.php:494 #. translators: Date query invalid date message: 1: invalid value, 2: type of #. value, 3: minimum valid value, 4: maximum valid value @@ -243,27 +277,12 @@ msgstr "" msgid "The following values do not describe a valid date: month %1$s, day %2$s." msgstr "" -#. Plugin Name of the plugin/theme -msgid "ElasticPress" -msgstr "" - -#: includes/dashboard-page.php:26 -msgid "" -"ElasticPress, the fast and flexible query engine for WordPress, let's you " -"supercharge your website through a variety of modules. Activate the ones " -"you need below:" -msgstr "" - -#: includes/dashboard-page.php:66 -msgid "Deactivate" -msgstr "" - -#: includes/dashboard-page.php:67 -msgid "Activate" +#: includes/dashboard-page.php:24 +msgid "List of modules" msgstr "" -#: includes/dashboard-page.php:68 -msgid "Syncing..." +#: includes/dashboard-page.php:53 +msgid "settings" msgstr "" #: includes/intro-page.php:24 @@ -332,28 +351,46 @@ msgid "" "your admin search." msgstr "" -#: modules/related-posts/related-posts.php:85 -msgid "Related Posts" +#: modules/admin/admin.php:127 +msgid "" +"You aren't using ElasticPress.io so " +"we can't be sure your Elasticsearch instance is secure." msgstr "" -#: modules/related-posts/related-posts.php:127 -msgid "" -"Help users easily find related content by adding related posts to the end " -"of each post." +#: modules/related-posts/related-posts.php:76 +msgid "Help users easily find related content with a widget that just works." msgstr "" -#: modules/related-posts/related-posts.php:138 +#: modules/related-posts/related-posts.php:87 msgid "" "Showing users related content is a quick way to improve readership and " -"loyalty. There a number of plugins that show related content, most of which " -"are ineffective and slow." +"loyalty. There are a number of plugins that show related content, most of " +"which are ineffective and slow." msgstr "" -#: modules/related-posts/related-posts.php:140 +#: modules/related-posts/related-posts.php:89 msgid "" "ElasticPress has a powerful content matching algorithm that lets it find " -"related content very effectively. This module will show three related posts " -"after the post content." +"related content very effectively. This module will create a widget for you " +"to place into any sidebar or widgetized area." +msgstr "" + +#: modules/related-posts/widget.php:17 +msgid "" +"Show related posts using ElasticPress. This widget will only appear on " +"single post, page, and custom type pages." +msgstr "" + +#: modules/related-posts/widget.php:18 +msgid "ElasticPress - Related Posts" +msgstr "" + +#: modules/related-posts/widget.php:88 +msgid "Title:" +msgstr "" + +#: modules/related-posts/widget.php:96 +msgid "Number of Posts to Show:" msgstr "" #: modules/search/search.php:16 @@ -386,7 +423,7 @@ msgid "" "it's activated by default." msgstr "" -#: modules/woocommerce/woocommerce.php:529 +#: modules/woocommerce/woocommerce.php:533 msgid "" "Allow customers to filter through products faster and improve product " "search relevancy. Enable editors to find orders and products more " @@ -394,7 +431,7 @@ msgid "" "and reduce administrative costs." msgstr "" -#: modules/woocommerce/woocommerce.php:540 +#: modules/woocommerce/woocommerce.php:544 msgid "" "Running eCommerce stores is hard enough already. You should not have to " "worry about slow load times. ElasticPress WooCommerce supercharges all " @@ -403,15 +440,15 @@ msgid "" "fast." msgstr "" -#: modules/woocommerce/woocommerce.php:542 +#: modules/woocommerce/woocommerce.php:546 msgid "" "In the admin, order management and fulfillment is supercharged. Finding " "orders is much easier with more relevant searches. View order lists is " "easier since they load faster." msgstr "" -#: modules/woocommerce/woocommerce.php:556 -msgid "WooCommerce must be active to use this module." +#: modules/woocommerce/woocommerce.php:560 +msgid "WooCommerce not installed." msgstr "" #: vendor/woocommerce/i18n/continents.php:19 @@ -7180,16 +7217,6 @@ msgstr "" msgid "Expiry date" msgstr "" -#: vendor/woocommerce/includes/admin/class-wc-admin-post-types.php:272 -#: vendor/woocommerce/includes/admin/class-wc-admin-webhooks-table-list.php:41 -#: vendor/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-product-data.php:671 -#: vendor/woocommerce/includes/admin/settings/views/html-webhook-log.php:25 -#: vendor/woocommerce/includes/admin/settings/views/html-webhooks-edit.php:24 -#: vendor/woocommerce/includes/wc-account-functions.php:178 -#: vendor/woocommerce/templates/myaccount/my-orders.php:15 -msgid "Status" -msgstr "" - #: vendor/woocommerce/includes/admin/class-wc-admin-post-types.php:273 #: vendor/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-data.php:165 #: vendor/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-request.php:195 @@ -9729,11 +9756,6 @@ msgstr "" msgid "Add shipping cost" msgstr "" -#: vendor/woocommerce/includes/admin/meta-boxes/views/html-order-items.php:246 -#: vendor/woocommerce/templates/myaccount/form-reset-password.php:47 -msgid "Save" -msgstr "" - #: vendor/woocommerce/includes/admin/meta-boxes/views/html-order-items.php:256 msgid "Restock refunded items" msgstr "" @@ -9873,16 +9895,6 @@ msgstr "" msgid "Enter a SKU for this variation or leave blank to use the parent product SKU." msgstr "" -#: vendor/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php:76 -#: vendor/woocommerce/includes/admin/settings/class-wc-settings-checkout.php:292 -#: vendor/woocommerce/includes/admin/settings/class-wc-settings-emails.php:269 -#: vendor/woocommerce/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php:20 -#: vendor/woocommerce/includes/admin/views/html-admin-page-status-tools.php:31 -#: vendor/woocommerce/includes/admin/views/html-admin-page-status-tools.php:42 -#: vendor/woocommerce/includes/admin/views/html-admin-page-status-tools.php:53 -msgid "Enabled" -msgstr "" - #: vendor/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php:78 msgid "" "Enable this option if access is given to a downloadable file upon purchase " @@ -10845,11 +10857,6 @@ msgstr "" msgid "Manual" msgstr "" -#: vendor/woocommerce/includes/admin/settings/class-wc-settings-emails.php:271 -#: vendor/woocommerce/includes/wc-webhook-functions.php:25 -msgid "Disabled" -msgstr "" - #: vendor/woocommerce/includes/admin/settings/class-wc-settings-emails.php:283 msgid "Configure" msgstr "" @@ -20957,6 +20964,10 @@ msgstr "" msgid "Drag widgets here to remove them from the sidebar but keep their settings." msgstr "" +#. Plugin Name of the plugin/theme +msgid "ElasticPress" +msgstr "" + #. Description of the plugin/theme msgid "A fast and flexible search and query engine for WordPress." msgstr "" diff --git a/modules/admin/admin.php b/modules/admin/admin.php index 7883d501fe..cea18db223 100644 --- a/modules/admin/admin.php +++ b/modules/admin/admin.php @@ -93,7 +93,6 @@ function ep_admin_module_box_long() {

-

code = 1; + $status->message = __( "You aren't using ElasticPress.io so we can't be sure your Elasticsearch instance is secure.", 'elasticpress' ); + } + + return $status; +} + /** * Register the module */ ep_register_module( 'admin', array( 'title' => 'Admin', 'setup_cb' => 'ep_admin_setup', + 'requirements_status_cb' => 'ep_admin_requirements_status', 'module_box_summary_cb' => 'ep_admin_module_box_summary', 'module_box_long_cb' => 'ep_admin_module_box_long', 'requires_install_reindex' => true, diff --git a/modules/related-posts/related-posts.php b/modules/related-posts/related-posts.php index 99a9d3caca..ca03237071 100644 --- a/modules/related-posts/related-posts.php +++ b/modules/related-posts/related-posts.php @@ -10,11 +10,11 @@ function ep_related_posts_formatted_args( $formatted_args, $args ) { if ( ! empty( $args[ 'more_like' ] ) ) { $formatted_args[ 'query' ] = array( 'more_like_this' => array( - 'ids' => is_array( $args[ 'more_like' ] ) ? $args[ 'more_like' ] : array( $args[ 'more_like' ] ), - 'fields' => apply_filters( 'ep_related_posts_fields', array( 'post_title', 'post_content', 'terms.post_tag.name' ) ), - 'min_term_freq' => 1, - 'max_query_terms' => 12, - 'min_doc_freq' => 1, + 'ids' => is_array( $args[ 'more_like' ] ) ? $args[ 'more_like' ] : array( $args[ 'more_like' ] ), + 'fields' => apply_filters( 'ep_related_posts_fields', array( 'post_title', 'post_content', 'terms.post_tag.name' ) ), + 'min_term_freq' => 1, + 'max_query_terms' => 12, + 'min_doc_freq' => 1, ) ); } @@ -22,31 +22,6 @@ function ep_related_posts_formatted_args( $formatted_args, $args ) { return $formatted_args; } -/** - * Add related posts HTML to the content - * - * @param string $content - * @since 2.1 - * @return string - */ -function ep_related_posts_filter_content( $content ) { - if ( is_search() || is_home() || is_archive() || is_category() ) { - return $content; - } - $post_id = get_the_ID(); - $cache_key = md5( 'related_posts_' . $post_id ); - $related_posts = wp_cache_get( $cache_key, 'ep-related-posts' ); - - if ( false === $related_posts ) { - $related_posts = ep_find_related( $post_id ); - wp_cache_set( $cache_key, $related_posts, 'ep-related-posts', 300 ); - } - - $html = ep_related_get_html( $related_posts ) - ; - return $content . "\n" . $html; -} - /** * Search Elasticsearch for related content * @@ -55,11 +30,11 @@ function ep_related_posts_filter_content( $content ) { * @since 2.1 * @return array|bool */ -function ep_find_related( $post_id, $return = 4 ) { +function ep_find_related( $post_id, $return = 5 ) { $args = array( 'more_like' => $post_id, 'posts_per_page' => $return, - 's' => '' + 'ep_integrate' => true, ); $query = new WP_Query( apply_filters( 'ep_find_related_args', $args ) ); @@ -71,50 +46,24 @@ function ep_find_related( $post_id, $return = 4 ) { } /** - * Generate related posts html - * - * @param array $posts + * Setup all module filters + * * @since 2.1 - * @return string */ -function ep_related_get_html( $posts ) { - if ( false === $posts ) { - return ''; - } - - $html = '

' . esc_html__( 'Related Posts', 'elasticpress' ) . '

'; - $html .= '
    '; - - foreach ( $posts as $post ) { - $html.=sprintf( - '
  • %s
  • ', esc_url( get_permalink( $post->ID ) ), esc_html( $post->post_title ) - ); - } - - $html .= '
'; - - do_action( 'ep_related_html_attached', $posts ); - - /** - * Filter the display HTML for related posts. - * - * If developers want to customize the returned HTML for related posts or - * write their own HTML, they have the power to do so. - * - * @param string $html Default Generated HTML - * @param array $posts Array of WP_Post objects. - */ - return apply_filters( 'ep_related_html', $html, $posts ); +function ep_related_posts_setup() { + add_action( 'widgets_init', 'ep_related_posts_register_widget' ); + add_filter( 'ep_formatted_args', 'ep_related_posts_formatted_args', 10, 2 ); } /** - * Setup all module filters + * Register related posts widget * - * @since 2.1 + * @since 2.2 */ -function ep_related_posts_setup() { - add_filter( 'ep_formatted_args', 'ep_related_posts_formatted_args', 10, 2 ); - add_filter( 'the_content', 'ep_related_posts_filter_content' ); +function ep_related_posts_register_widget() { + require_once( dirname( __FILE__ ) . '/widget.php' ); + + register_widget( 'EP_Related_Posts_Widget' ); } /** @@ -124,7 +73,7 @@ function ep_related_posts_setup() { */ function ep_related_posts_module_box_summary() { ?> -

+

-

+

-

+

esc_html__( 'Show related posts using ElasticPress. This widget will only appear on single post, page, and custom type pages.', 'elasticpress' ) ); + parent::__construct( 'ep-related-posts', esc_html__( 'ElasticPress - Related Posts', 'elasticpress' ), $options ); + } + + /** + * Display widget + * + * @param array $args + * @param array $instance + * @since 2.2 + */ + public function widget( $args, $instance ) { + + if ( ! is_single() ) { + return; + } + + $related_posts = get_transient( 'ep_related_posts_' . get_the_ID() ); + + if ( false === $related_posts ) { + $related_posts = ep_find_related( get_the_ID(), $instance['num_posts'] ); + + if ( empty( $related_posts ) ) { + if ( ! defined( 'WP_DEBUG') || ! WP_DEBUG ) { + set_transient( 'ep_related_posts_' . get_the_ID(), '', HOUR_IN_SECONDS ); // Let's not spam + } + return; + } + + ob_start(); + + echo $args['before_widget']; + + if ( ! empty( $instance['title'] ) ) { + echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title']; + } + ?> + + + + +

+ + + +

+ +

+ + + +

+

- -

- must have for all websites which is why it's activated by default.", 'elasticpress' ); ?> -

code = 2; + $status->message = esc_html__( 'WooCommerce not installed.', 'elasticpress' ); } + + return $status; } /** @@ -567,6 +569,7 @@ function wc_dependencies_met_cb() { ep_register_module( 'woocommerce', array( 'title' => 'WooCommerce', 'setup_cb' => 'ep_wc_setup', + 'requirements_status_cb' => 'ep_wc_requirements_status', 'module_box_summary_cb' => 'ep_wc_module_box_summary', 'module_box_long_cb' => 'ep_wc_module_box_long', 'requires_install_reindex' => true, From 5f0d94eb210e999aa351909c7bb1964028c3742f Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 13 Nov 2016 17:39:44 -0500 Subject: [PATCH 030/159] Add composer.lock to VC; start preparing plugin for 2.2 --- .gitignore | 1 - README.md | 15 +- composer.lock | 2457 ++++++++++++++++++++++++++++++++++++++++++++++ elasticpress.php | 4 +- readme.txt | 16 +- 5 files changed, 2477 insertions(+), 16 deletions(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 34559919d2..afd3597bef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /vendor/ -composer.lock node_modules .idea \ No newline at end of file diff --git a/README.md b/README.md index 930f5386e2..8e0372e874 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ A fast and flexible search and query engine for WordPress. **Upgrade Notice:** Versions 1.6.1, 1.6.2, 1.7, 1.8, 2.1, 2.1.2 require re-syncing. -ElasticPress, a fast and flexible search and query engine for WordPress, enables WordPress to find or “query” relevant content extremely fast through a variety of highly customizable modules. WordPress out-of-the-box struggles to analyze content relevancy and can be very slow. ElasticPress supercharges your WordPress website making for happier users and administrators. - -ElasticPress is module based so you can pick and choose what you need. The plugin even contains modules for popular plugins. +ElasticPress, a fast and flexible search and query engine for WordPress, enables WordPress to find or “query” relevant content extremely fast through a variety of highly customizable features. WordPress out-of-the-box struggles to analyze content relevancy and can be very slow. ElasticPress supercharges your WordPress website making for happier users and administrators. The plugin even contains features for popular plugins.

@@ -30,12 +28,11 @@ ElasticPress integrates with the [WP_Query](http://codex.wordpress.org/Class_Ref 2. Install the plugin in WordPress. You can download a [zip via Github](https://github.com/10up/ElasticPress/archive/master.zip) and upload it using the WordPress plugin uploader. 3. Activate the plugin (network activate for multisite). Navigate to the settings page. You should see an ElasticPress icon in your admin menu. 4. Input your Elasticsearch host. Your host must begin with a protocol specifier (`http` or `https`). URLs without a protocol prefix will not be parsed correctly and will cause ElasticPress to error out. -5. Activate the ElasticPress modules you want to use. Search is activated by default. -6. Sync your content by clicking the sync icon. +5. Sync your content by clicking the sync icon. Once syncing finishes, your site is officially supercharged. You also have access to ElasticPress's powerful WP_Query integration API. -## Available Modules +## Features ### Search @@ -43,7 +40,7 @@ Beef up your search to be more accurate, search tags, categories, and other taxo ### WooCommerce -Allow customers to filter through products faster and improve product search relevancy. Enable editors to find orders and products more effectively in the admin. This module will increase your sales bottom line and reduce administrative costs. +Allow customers to filter through products faster and improve product search relevancy. Enable editors to find orders and products more effectively in the admin. This feature will increase your sales bottom line and reduce administrative costs. ### Related Posts @@ -55,7 +52,7 @@ Help editors more effectively browse through content. Load long lists of posts f ## `WP_Query` and the ElasticPress Query Integration -ElasticPress integrates with `WP_Query` if the `ep_integrate` parameter is passed (see below) to the query object. If the search module is activated, all queries with the `s` parameter will be integrated with as well. ElasticPress converts `WP_Query` arguments to Elasticsearch readable queries. Supported `WP_Query` parameters are listed and explained below. ElasticPress also adds some extra `WP_query` arguments for extra functionality. +ElasticPress integrates with `WP_Query` if the `ep_integrate` parameter is passed (see below) to the query object. If the search feature is activated (which it is by default), all queries with the `s` parameter will be integrated with as well. ElasticPress converts `WP_Query` arguments to Elasticsearch readable queries. Supported `WP_Query` parameters are listed and explained below. ElasticPress also adds some extra `WP_query` arguments for extra functionality. ### Supported WP_Query Parameters @@ -92,7 +89,7 @@ ElasticPress integrates with `WP_Query` if the `ep_integrate` parameter is passe * ```s``` (*string*) - Search keyword. By default used to search against ```post_title```, ```post_content```, and ```post_excerpt```. (Requires search module) + Search keyword. By default used to search against ```post_title```, ```post_content```, and ```post_excerpt```. (Requires search feature) * ```posts_per_page``` (*int*) diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000000..ec9ecb6bb8 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2457 @@ +{ + "_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": "5acefaef01f73297640f746393d88738", + "content-hash": "cddfe9d6a35a7fd274871696c0c65143", + "packages": [], + "packages-dev": [ + { + "name": "composer/ca-bundle", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "5df9ed0ed0c9506ea6404a23450854e5df15cc12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/5df9ed0ed0c9506ea6404a23450854e5df15cc12", + "reference": "5df9ed0ed0c9506ea6404a23450854e5df15cc12", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "symfony/process": "^2.5 || ^3.0" + }, + "suggest": { + "symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2016-07-18 23:07:53" + }, + { + "name": "composer/composer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "b49a006748a460f8dae6500ec80ed021501ce969" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/b49a006748a460f8dae6500ec80ed021501ce969", + "reference": "b49a006748a460f8dae6500ec80ed021501ce969", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/semver": "^1.0", + "composer/spdx-licenses": "^1.0", + "justinrainbow/json-schema": "^1.6 || ^2.0", + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0", + "seld/cli-prompt": "^1.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.5 || ^3.0", + "symfony/filesystem": "^2.5 || ^3.0", + "symfony/finder": "^2.2 || ^3.0", + "symfony/process": "^2.1 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "time": "2016-07-18 23:28:52" + }, + { + "name": "composer/installers", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "a3595c5272a6f247228abb20076ed27321e4aae9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/a3595c5272a6f247228abb20076ed27321e4aae9", + "reference": "a3595c5272a6f247228abb20076ed27321e4aae9", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "replace": { + "roundcube/plugin-installer": "*", + "shama/baton": "*" + }, + "require-dev": { + "composer/composer": "1.0.*@dev", + "phpunit/phpunit": "4.1.*" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Craft", + "Dolibarr", + "Hurad", + "ImageCMS", + "MODX Evo", + "Mautic", + "OXID", + "RadPHP", + "SMF", + "Thelia", + "WolfCMS", + "agl", + "aimeos", + "annotatecms", + "bitrix", + "cakephp", + "chef", + "codeigniter", + "concrete5", + "croogo", + "dokuwiki", + "drupal", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "joomla", + "kohana", + "laravel", + "lithium", + "magento", + "mako", + "mediawiki", + "modulework", + "moodle", + "phpbb", + "piwik", + "ppi", + "puppet", + "roundcube", + "shopware", + "silverstripe", + "symfony", + "typo3", + "wordpress", + "zend", + "zikula" + ], + "time": "2016-07-05 06:18:20" + }, + { + "name": "composer/semver", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "03c9de5aa25e7672c4ad251eeaba0c47a06c8b98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/03c9de5aa25e7672c4ad251eeaba0c47a06c8b98", + "reference": "03c9de5aa25e7672c4ad251eeaba0c47a06c8b98", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2016-06-02 09:04:51" + }, + { + "name": "composer/spdx-licenses", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/88c26372b1afac36d8db601cdf04ad8716f53d88", + "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "time": "2016-05-04 12:27:30" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "justinrainbow/json-schema", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "6b2a33e6a768f96bdc2ead5600af0822eed17d67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/6b2a33e6a768f96bdc2ead5600af0822eed17d67", + "reference": "6b2a33e6a768f96bdc2ead5600af0822eed17d67", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "json-schema/json-schema-test-suite": "1.2.0", + "phpdocumentor/phpdocumentor": "~2", + "phpunit/phpunit": "^4.8.22" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2016-06-02 10:59:52" + }, + { + "name": "mustache/mustache", + "version": "v2.11.1", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "a3f6d55996dd28b58f3a909d474189a3c1aa693f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/a3f6d55996dd28b58f3a909d474189a3c1aa693f", + "reference": "a3f6d55996dd28b58f3a909d474189a3c1aa693f", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "time": "2016-07-31 06:18:27" + }, + { + "name": "mustangostang/spyc", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/mustangostang/spyc.git", + "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/dc4785b4d7227fd9905e086d499fb8abfadf9977", + "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "Spyc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT License" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP", + "homepage": "https://github.com/mustangostang/spyc/", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "time": "2013-02-21 10:52:01" + }, + { + "name": "nb/oxymel", + "version": "v0.1.0", + "source": { + "type": "git", + "url": "https://github.com/nb/oxymel.git", + "reference": "cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nb/oxymel/zipball/cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c", + "reference": "cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "type": "library", + "autoload": { + "psr-0": { + "Oxymel": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nikolay Bachiyski", + "email": "nb@nikolay.bg", + "homepage": "http://extrapolate.me/" + } + ], + "description": "A sweet XML builder", + "homepage": "https://github.com/nb/oxymel", + "keywords": [ + "xml" + ], + "time": "2013-02-24 15:01:54" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90", + "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-07-21 06:48:14" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "ramsey/array_column", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/array_column.git", + "reference": "f8e52eb28e67eb50e613b451dd916abcf783c1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/array_column/zipball/f8e52eb28e67eb50e613b451dd916abcf783c1db", + "reference": "f8e52eb28e67eb50e613b451dd916abcf783c1db", + "shasum": "" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "0.8.*", + "phpunit/phpunit": "~4.5", + "satooshi/php-coveralls": "0.6.*", + "squizlabs/php_codesniffer": "~2.2" + }, + "type": "library", + "autoload": { + "files": [ + "src/array_column.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "homepage": "http://benramsey.com" + } + ], + "description": "Provides functionality for array_column() to projects using PHP earlier than version 5.5.", + "homepage": "https://github.com/ramsey/array_column", + "keywords": [ + "array", + "array_column", + "column" + ], + "time": "2015-03-20 22:07:39" + }, + { + "name": "rmccue/requests", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/rmccue/Requests.git", + "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rmccue/Requests/zipball/6aac485666c2955077d77b796bbdd25f0013a4ea", + "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "psr-0": { + "Requests": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "http://ryanmccue.info" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "http://github.com/rmccue/Requests", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "time": "2014-05-18 04:59:02" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18 05:49:44" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "seld/cli-prompt", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/cli-prompt.git", + "reference": "8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4", + "reference": "8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\CliPrompt\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", + "keywords": [ + "cli", + "console", + "hidden", + "input", + "prompt" + ], + "time": "2016-04-18 09:31:41" + }, + { + "name": "seld/jsonlint", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "66834d3e3566bb5798db7294619388786ae99394" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/66834d3e3566bb5798db7294619388786ae99394", + "reference": "66834d3e3566bb5798db7294619388786ae99394", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "time": "2015-11-21 02:21:41" + }, + { + "name": "seld/phar-utils", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phra" + ], + "time": "2015-10-13 18:44:15" + }, + { + "name": "symfony/config", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "4275ef5b59f18959df0eee3991e9ca0cc208ffd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/4275ef5b59f18959df0eee3991e9ca0cc208ffd4", + "reference": "4275ef5b59f18959df0eee3991e9ca0cc208ffd4", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:02:44" + }, + { + "name": "symfony/console", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "36e62335caca8a6e909c5c5bac4a8128149911c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/36e62335caca8a6e909c5c5bac4a8128149911c9", + "reference": "36e62335caca8a6e909c5c5bac4a8128149911c9", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2016-07-30 07:20:35" + }, + { + "name": "symfony/dependency-injection", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f2b5a00d176f6a201dc430375c0ef37706ea3d12", + "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/expression-language": "<2.6" + }, + "require-dev": { + "symfony/config": "~2.2|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/yaml": "~2.3.42|~2.7.14|~2.8.7|~3.0.7" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2016-07-30 07:20:35" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8", + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2016-07-28 16:56:28" + }, + { + "name": "symfony/filesystem", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "ab4c3f085c8f5a56536845bf985c4cef30bf75fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ab4c3f085c8f5a56536845bf985c4cef30bf75fd", + "reference": "ab4c3f085c8f5a56536845bf985c4cef30bf75fd", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2016-07-20 05:41:28" + }, + { + "name": "symfony/finder", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "60804d88691e4a73bbbb3035eb1d9f075c5c2c10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/60804d88691e4a73bbbb3035eb1d9f075c5c2c10", + "reference": "60804d88691e4a73bbbb3035eb1d9f075c5c2c10", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2016-07-26 08:02:44" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "dff51f72b0706335131b00a7f49606168c582594" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", + "reference": "dff51f72b0706335131b00a7f49606168c582594", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-05-18 14:26:46" + }, + { + "name": "symfony/process", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d20332e43e8774ff8870b394f3dd6020cc7f8e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d20332e43e8774ff8870b394f3dd6020cc7f8e0c", + "reference": "d20332e43e8774ff8870b394f3dd6020cc7f8e0c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2016-07-28 11:13:19" + }, + { + "name": "symfony/translation", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "32b0c824da6df065f43b0c458dc505940e98a7f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/32b0c824da6df065f43b0c458dc505940e98a7f1", + "reference": "32b0c824da6df065f43b0c458dc505940e98a7f1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8", + "symfony/intl": "~2.4|~3.0.0", + "symfony/yaml": "~2.2|~3.0.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2016-07-30 07:20:35" + }, + { + "name": "symfony/yaml", + "version": "v2.8.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "0ceab136f43ed9d3e97b3eea32a7855dc50c121d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0ceab136f43ed9d3e97b3eea32a7855dc50c121d", + "reference": "0ceab136f43ed9d3e97b3eea32a7855dc50c121d", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-07-17 09:06:15" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.11.1", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "5311a4b99103c0505db015a334be4952654d6e21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/5311a4b99103c0505db015a334be4952654d6e21", + "reference": "5311a4b99103c0505db015a334be4952654d6e21", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "cli": "lib/" + }, + "files": [ + "lib/cli/cli.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + }, + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "time": "2016-02-08 14:34:01" + }, + { + "name": "wp-cli/wp-cli", + "version": "v0.24.1", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/wp-cli.git", + "reference": "97424dc18431a2d4d10e590157b57b9ea4a88da4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/97424dc18431a2d4d10e590157b57b9ea4a88da4", + "reference": "97424dc18431a2d4d10e590157b57b9ea4a88da4", + "shasum": "" + }, + "require": { + "composer/composer": "^1.0.0", + "composer/semver": "~1.0", + "mustache/mustache": "~2.4", + "mustangostang/spyc": "~0.5", + "nb/oxymel": "~0.1.0", + "php": ">=5.3.29", + "ramsey/array_column": "~1.1", + "rmccue/requests": "~1.6", + "symfony/config": "~2.7", + "symfony/console": "~2.7", + "symfony/dependency-injection": "~2.7", + "symfony/event-dispatcher": "~2.7", + "symfony/filesystem": "~2.7", + "symfony/finder": "~2.7", + "symfony/process": "~2.1", + "symfony/translation": "~2.7", + "symfony/yaml": "~2.7", + "wp-cli/php-cli-tools": "~0.11.1" + }, + "require-dev": { + "behat/behat": "2.5.*", + "phpunit/phpunit": "3.7.*" + }, + "suggest": { + "psy/psysh": "Enhanced `wp shell` functionality" + }, + "bin": [ + "bin/wp.bat", + "bin/wp" + ], + "type": "library", + "autoload": { + "psr-0": { + "WP_CLI": "php" + }, + "classmap": [ + "php/export" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A command line interface for WordPress", + "homepage": "http://wp-cli.org", + "keywords": [ + "cli", + "wordpress" + ], + "time": "2016-08-09 13:25:56" + }, + { + "name": "wpackagist-plugin/woocommerce", + "version": "dev-trunk", + "source": { + "type": "svn", + "url": "https://plugins.svn.wordpress.org/woocommerce/", + "reference": "trunk" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/plugin/woocommerce.zip", + "reference": null, + "shasum": null + }, + "require": { + "composer/installers": "~1.0" + }, + "type": "wordpress-plugin", + "homepage": "https://wordpress.org/plugins/woocommerce/", + "time": "2016-07-26 17:31:18" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "phpunit/phpunit": 0, + "wp-cli/wp-cli": 0, + "wpackagist-plugin/woocommerce": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.2" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.3.29" + } +} diff --git a/elasticpress.php b/elasticpress.php index a948968cbc..f599276594 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -3,7 +3,7 @@ /** * Plugin Name: ElasticPress * Description: A fast and flexible search and query engine for WordPress. - * Version: 2.1.2 + * Version: 2.2 * Author: Taylor Lovett, Matt Gross, Aaron Holbrook, 10up * Author URI: http://10up.com * License: GPLv2 or later @@ -22,7 +22,7 @@ define( 'EP_URL', plugin_dir_url( __FILE__ ) ); define( 'EP_PATH', plugin_dir_path( __FILE__ ) ); -define( 'EP_VERSION', '2.1.2' ); +define( 'EP_VERSION', '2.2' ); define( 'EP_MODULES_DIR', dirname( __FILE__ ) . '/modules' ); require_once( 'classes/class-ep-config.php' ); diff --git a/readme.txt b/readme.txt index eef612acc1..1c21d5d6f1 100644 --- a/readme.txt +++ b/readme.txt @@ -12,13 +12,13 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html A fast and flexible search and query engine for WordPress. == Description == -ElasticPress, a fast and flexible search and query engine for WordPress, enables WordPress to find or “query” relevant content extremely fast through a variety of highly customizable modules. WordPress out-of-the-box struggles to analyze content relevancy and can be very slow. ElasticPress supercharges your WordPress website making for happier users and administrators. +ElasticPress, a fast and flexible search and query engine for WordPress, enables WordPress to find or “query” relevant content extremely fast through a variety of highly customizable features. WordPress out-of-the-box struggles to analyze content relevancy and can be very slow. ElasticPress supercharges your WordPress website making for happier users and administrators. -Pick and choose the modules that makes sense for your website: +Here is a list of the amazing ElasticPress features included in the plugin: __Search__: Beef up your search to be more accurate, search tags, categories, and other taxonomies, catch misspellings, weight content by recency and more. -__WooCommerce__: Allow customers to filter through products faster and improve product search relevancy. Enable editors to find orders and products more effectively in the admin. This module will increase your sales bottom line and reduce administrative costs. +__WooCommerce__: Allow customers to filter through products faster and improve product search relevancy. Enable editors to find orders and products more effectively in the admin. This feature will increase your sales bottom line and reduce administrative costs. __Related Posts__: Help users easily find related content by adding related posts to the end of each post. @@ -30,10 +30,18 @@ Please refer to [Github](https://github.com/10up/ElasticPress) for detailed usag 1. First, you will need to properly [install and configure](http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/_installing_elasticsearch.html) Elasticsearch. 2. Activate the plugin in WordPress. 3. In the ElasticPress settings page, input your Elasticsearch host. -4. Activate the ElasticPress modules you need from the dashboard. +4. Enjoy! == Changelog == += 2.2 = + +Version 2.2 rethinks the module process to make ElasticPress a more complete query engine solution. Modules are now auto-on and really just features. Why would anyone want to not use amazing functionality to improve speed and relevancy on their website? Modules can of course be overriden and disabled. Also, modules that don't have their minimum requirements met are still auto-disabled. + +### Enhancements +* (Breaking change) Module registration API changed. See `register_module` in `classes/class-ep-modules.php`. +* (Breaking change) Related posts are now in a widget instead of automatically being appending to content. + = 2.1.2 (Requires re-index) = * Separate mapping for ES 5.0+ From 79b3829fef9d308f0b6b9c9a1f1954c3a355292d Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 13 Nov 2016 19:30:43 -0500 Subject: [PATCH 031/159] Rename module to feature --- README.md | 38 +-- assets/css/admin.css | 96 +++--- assets/css/admin.min.css | 2 +- assets/css/admin.scss | 68 ++--- assets/js/admin.js | 76 ++--- assets/js/admin.min.js | 2 +- bin/wp-cli.php | 92 +++--- classes/class-ep-dashboard.php | 32 +- classes/class-ep-feature.php | 277 ++++++++++++++++++ classes/class-ep-features.php | 123 ++++++++ classes/class-ep-module.php | 277 ------------------ classes/class-ep-modules.php | 123 -------- elasticpress.php | 39 ++- {modules => features}/admin/admin.php | 28 +- .../related-posts/related-posts.php | 22 +- .../related-posts/widget.php | 0 {modules => features}/search/search.php | 22 +- .../woocommerce/woocommerce.php | 30 +- includes/dashboard-page.php | 28 +- includes/intro-page.php | 2 +- lang/elasticpress.pot | 198 ++++++------- tests/{modules => features}/test-admin.php | 28 +- .../test-related-posts.php | 12 +- tests/{modules => features}/test-search.php | 12 +- .../test-woocommerce.php | 30 +- tests/test-multisite.php | 4 +- tests/test-single-site.php | 32 +- uninstall.php | 4 +- 28 files changed, 843 insertions(+), 854 deletions(-) create mode 100644 classes/class-ep-feature.php create mode 100644 classes/class-ep-features.php delete mode 100644 classes/class-ep-module.php delete mode 100644 classes/class-ep-modules.php rename {modules => features}/admin/admin.php (83%) rename {modules => features}/related-posts/related-posts.php (79%) rename {modules => features}/related-posts/widget.php (100%) rename {modules => features}/search/search.php (87%) rename {modules => features}/woocommerce/woocommerce.php (95%) rename tests/{modules => features}/test-admin.php (87%) rename tests/{modules => features}/test-related-posts.php (92%) rename tests/{modules => features}/test-search.php (91%) rename tests/{modules => features}/test-woocommerce.php (86%) diff --git a/README.md b/README.md index 8e0372e874..59be2cf81e 100644 --- a/README.md +++ b/README.md @@ -508,17 +508,17 @@ The following commands are supported by ElasticPress: Recreates the alias index which points to every index in the network. -* `wp elasticpress activate-module [--network-wide]` +* `wp elasticpress activate-feature [--network-wide]` - Activate a module. If a re-indexing is required, you will need to do it manually. `--network-wide` will affect network activated ElasticPress. + Activate a feature. If a re-indexing is required, you will need to do it manually. `--network-wide` will affect network activated ElasticPress. -* `wp elasticpress deactivate-module [--network-wide]` +* `wp elasticpress deactivate-feature [--network-wide]` - Deactivate a module. `--network-wide` will affect network activated ElasticPress. + Deactivate a feature. `--network-wide` will affect network activated ElasticPress. -* `wp elasticpress list-modules [--all] [--network-wide]` +* `wp elasticpress list-features [--all] [--network-wide]` - Lists active modules. `--all` will show all registered modules. `--network-wide` will force checking network options as opposed to a single sites options. + Lists active features. `--all` will show all registered features. `--network-wide` will force checking network options as opposed to a single sites options. * `wp elasticpress stats` @@ -536,17 +536,17 @@ The following commands are supported by ElasticPress: define( 'ES_SHIELD', 'username:password' ); ``` -## Custom Modules +## Custom Features -ElasticPress has a robust API for registering your own modules. Refer to the code within each module for detailed examples. To register a module, you will need to call the `ep_register_module()` function like so: +ElasticPress has a robust API for registering your own features. Refer to the code within each feature for detailed examples. To register a feature, you will need to call the `ep_register_feature()` function like so: ```php add_action( 'plugins_loaded', function() { - ep_register_module( 'slug', array( + ep_register_feature( 'slug', array( 'title' => 'Pretty Title', 'setup_cb' => 'setup_callback_function', - 'module_box_summary_cb' => 'summary_callback_function', - 'module_box_long_cb' => 'long_summary_callback_function', + 'feature_box_summary_cb' => 'summary_callback_function', + 'feature_box_long_cb' => 'long_summary_callback_function', 'requires_install_reindex' => true, 'dependencies_met_cb' => 'dependencies_meta_callback_function', 'post_activation_cb' => 'post_activation_callback_function', @@ -556,15 +556,15 @@ add_action( 'plugins_loaded', function() { The only arguments that are really required are the `slug` and `title` of the associative arguments array. Here are descriptions of each of the associative arguments: -* `title` (string) - Pretty title for module -* `requires_install_reindex` (boolean) - Setting to true will force a reindex after the module is activated. -* `setup_cb` (callback) - Callback to a function to be called on each page load when the module is activated. -* `post_activation_cb` (callback) - Callback to a function to be called after a module is first activated. -* `module_box_summary_cb` (callback) - Callback to a function that outputs HTML module box summary (short description of module). -* `module_box_long_cb` (callback) - Callback to a function that outputs HTML module box full description. -* `dependencies_met_cb` (callback) - Callback to a function that determines if the modules dependencies are met. True means yes, WP_Error means no. If no, WP_Error message will be printed to the screen. +* `title` (string) - Pretty title for feature +* `requires_install_reindex` (boolean) - Setting to true will force a reindex after the feature is activated. +* `setup_cb` (callback) - Callback to a function to be called on each page load when the feature is activated. +* `post_activation_cb` (callback) - Callback to a function to be called after a feature is first activated. +* `feature_box_summary_cb` (callback) - Callback to a function that outputs HTML feature box summary (short description of feature). +* `feature_box_long_cb` (callback) - Callback to a function that outputs HTML feature box full description. +* `dependencies_met_cb` (callback) - Callback to a function that determines if the features dependencies are met. True means yes, WP_Error means no. If no, WP_Error message will be printed to the screen. -If you build an open source custom module, let us know! We'd be happy to list the module within ElasticPress documentation. +If you build an open source custom feature, let us know! We'd be happy to list the feature within ElasticPress documentation. ## Development diff --git a/assets/css/admin.css b/assets/css/admin.css index e0ce436bc0..f9bbc2c80e 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -18,7 +18,7 @@ margin-top: 0; margin-bottom: 20px; } -h2.ep-list-modules { +h2.ep-list-features { display: none; } /** @@ -93,9 +93,9 @@ h2.ep-list-modules { background-color: #D84440; } /** - * Modules + * Features */ -.ep-modules { +.ep-features { overflow: auto; } .error-overlay.cant-connect, @@ -118,13 +118,13 @@ h2.ep-list-modules { top: 32px; left: 160px; } } -.ep-module .postbox { +.ep-feature .postbox { margin-bottom: 0; } -.ep-module .postbox .hndle { +.ep-feature .postbox .hndle { cursor: inherit; } -.ep-module .postbox .hndle .settings-button { +.ep-feature .postbox .hndle .settings-button { float: right; display: inline-block; font-size: 13px; @@ -137,7 +137,7 @@ h2.ep-list-modules { position: relative; padding-left: 23px; } -.ep-module .settings-button:before { +.ep-feature .settings-button:before { content: "\f140"; font: 400 19px/1 dashicons; display: inline-block; @@ -148,7 +148,7 @@ h2.ep-list-modules { position: absolute; left: 1px; } -.ep-module .settings-button:after { +.ep-feature .settings-button:after { border-radius: 50%; content: ' '; display: inline-block; @@ -158,39 +158,39 @@ h2.ep-list-modules { margin-left: 10px; margin-top: -2px; } -.module-requirements-status-2.ep-module .postbox .hndle .settings-button:after { +.feature-requirements-status-2.ep-feature .postbox .hndle .settings-button:after { background-color: transparent; border: 1px solid #ff0000; } -.module-requirements-status-2.ep-module .settings .requirements-status-notice { +.feature-requirements-status-2.ep-feature .settings .requirements-status-notice { border-color: #ff0000; } -.module-requirements-status-1.ep-module .postbox .hndle .settings-button:after { +.feature-requirements-status-1.ep-feature .postbox .hndle .settings-button:after { background-color: transparent; border: 1px solid #e3e600; } -.module-requirements-status-1.ep-module.module-active .postbox .hndle .settings-button:after { +.feature-requirements-status-1.ep-feature.feature-active .postbox .hndle .settings-button:after { background-color: #e3e600; } -.module-requirements-status-1.ep-module .settings .requirements-status-notice { +.feature-requirements-status-1.ep-feature .settings .requirements-status-notice { border-color: #e3e600; } -.module-requirements-status-0.ep-module .postbox .hndle .settings-button:after { +.feature-requirements-status-0.ep-feature .postbox .hndle .settings-button:after { background-color: transparent; border: 1px solid #6aa000; } -.module-requirements-status-0.ep-module.module-active .postbox .hndle .settings-button:after { +.feature-requirements-status-0.ep-feature.feature-active .postbox .hndle .settings-button:after { background-color: #6aa000; } -.module-requirements-status-0.ep-module .settings .requirements-status-notice { +.feature-requirements-status-0.ep-feature .settings .requirements-status-notice { border-color: #6aa000; } -.ep-module { +.ep-feature { position: relative; vertical-align: top; margin-bottom: 20px; } -.ep-module.saving .action-wrap:before { +.ep-feature.saving .action-wrap:before { content: ' '; vertical-align: middle; margin-right: 1.4em; @@ -212,78 +212,78 @@ h2.ep-list-modules { -webkit-animation: load8 1.1s infinite linear; animation: load8 1.1s infinite linear; } -.ep-module .description, -.ep-module .settings { +.ep-feature .description, +.ep-feature .settings { margin-bottom: 0; text-align: left; } -.ep-module .settings { +.ep-feature .settings { display: none; overflow: auto; } - .ep-module .settings h3 { + .ep-feature .settings h3 { margin-top: 0; font-size: inherit; font-weight: bold; } -.ep-module.show-settings .settings { +.ep-feature.show-settings .settings { display: block; } -.ep-module.show-settings .description { +.ep-feature.show-settings .description { display: none; } -.ep-module.show-settings .settings-button:before { +.ep-feature.show-settings .settings-button:before { content: "\f142"; } -.ep-module .settings .requirements-status-notice { +.ep-feature .settings .requirements-status-notice { border-left: 4px solid #6aa000; background-color: #efefef; padding: 8px 12px; margin-bottom: 10px; } -.ep-module .settings .action-wrap { +.ep-feature .settings .action-wrap { clear: both; text-align: right; margin-top: 10px; vertical-align: middle; } - .ep-module .settings .action-wrap a { + .ep-feature .settings .action-wrap a { cursor: pointer; display: inline-block; } - .ep-module .settings .action-wrap .cancel { + .ep-feature .settings .action-wrap .cancel { margin-top: 4px; margin-right: 6px; } -.ep-module .settings .field { +.ep-feature .settings .field { clear: both; overflow: auto; } - .ep-module .settings .field > label, - .ep-module .settings .field .field-name { + .ep-feature .settings .field > label, + .ep-feature .settings .field .field-name { display: block; float: left; margin-right: 3em; } - .ep-module .settings .field .input-wrap { + .ep-feature .settings .field .input-wrap { display: block; float: left; } - .ep-module .settings .field .disabled { + .ep-feature .settings .field .disabled { color: #c4c4c4; } - .ep-module .settings .field .disabled input { + .ep-feature .settings .field .disabled input { cursor: default; } -.ep-module .long { +.ep-feature .long { display: none; } - .ep-module .long p:last-child { + .ep-feature .long p:last-child { margin-bottom: 0; } -.ep-module.show-full .long { +.ep-feature.show-full .long { display: block; } -.ep-module .learn-more, -.ep-module .collapse { +.ep-feature .learn-more, +.ep-feature .collapse { cursor: pointer; } -.ep-module.show-full .learn-more { +.ep-feature.show-full .learn-more { display: none; } -.ep-module .learn-more:after { +.ep-feature .learn-more:after { content: "\f140"; font-family: dashicons; font-size: 1.5em; @@ -291,7 +291,7 @@ h2.ep-list-modules { top: -.07em; position: relative; } -.ep-module .collapse:after { +.ep-feature .collapse:after { content: "\f142"; font-family: dashicons; font-size: 1.5em; @@ -315,7 +315,7 @@ h2.ep-list-modules { .wrap.intro .is-dismissible { display: none !important; } -.modules-screenshot { +.features-screenshot { display: none; } .setup-message { @@ -362,26 +362,26 @@ h2.ep-list-modules { .intro .left { float: left; width: 30%; } - .modules-screenshot { + .features-screenshot { margin: 0 auto; max-width: 1000px; width: 70%; display: block; height: auto; float: right; } - .ep-modules .left { + .ep-features .left { display: block; float: left; box-sizing: border-box; padding-right: 10px; width: 50%; } - .ep-modules .right { + .ep-features .right { display: block; float: right; box-sizing: border-box; padding-left: 10px; width: 50%; } - .ep-module .module-message { + .ep-feature .feature-message { float: left; padding: 0; vertical-align: middle; diff --git a/assets/css/admin.min.css b/assets/css/admin.min.css index 957d79f299..d387ae561e 100644 --- a/assets/css/admin.min.css +++ b/assets/css/admin.min.css @@ -1 +1 @@ -.ep-header-menu,.wrap .notice,.wrap>h2{z-index:2;position:relative}.wrap h2{color:#888;line-height:1.75;margin:.5em 0 .75em}#wpbody #update-nag,#wpbody .update-nag{margin-left:-20px;width:100%;margin-top:0;margin-bottom:20px}h2.ep-list-modules{display:none}.ep-header-menu{background-color:#fff;margin-left:-20px;border-bottom:2px solid #ddd;padding:5px 20px}.ep-header-menu:after{content:' ';clear:both;display:block}.ep-header-menu img{float:left}.ep-header-menu .icons{float:right;padding-right:3px;height:39px;line-height:39px;display:inline-block}.ep-header-menu .icons .dashicons-update{font-size:29px;margin-right:10px;top:-3px}.ep-header-menu .icons a{font-size:27px;vertical-align:middle;color:inherit;position:relative;top:-3px;margin-left:8px;cursor:pointer}.ep-header-menu .icons .dashicons-controls-pause,.ep-header-menu .icons .dashicons-controls-play,.ep-header-menu .icons .dashicons-no{top:1px;font-size:30px}.ep-header-menu .cancel-sync,.ep-header-menu .pause-sync,.ep-header-menu .resume-sync{display:none}.ep-header-menu .sync-status{display:none;color:#666;bottom:-1px;position:relative;margin-right:8px;vertical-align:middle;font-style:italic}.ep-header-menu .progress-bar{margin-bottom:-5px;height:5px;position:absolute;bottom:0;left:0;background-color:#D84440}.ep-modules{overflow:auto}.error-overlay.cant-connect,.error-overlay.syncing{content:' ';display:block;position:fixed;top:46px;left:0;right:0;bottom:0;opacity:.6;background-color:#fff;z-index:1}@media (min-width:768px){.error-overlay,.error-overlay.cant-connect,.error-overlay.syncing{top:32px;left:160px}}.ep-module .postbox{margin-bottom:0}.ep-module .postbox .hndle{cursor:inherit}.ep-module .postbox .hndle .settings-button{float:right;display:inline-block;font-size:13px;background-color:#efefef;border-radius:4px;padding:4px 7px 4px 23px;margin-top:-4px;cursor:pointer;color:inherit;position:relative}.ep-module .settings-button:before{content:"\f140";font:400 19px/1 dashicons;display:inline-block;color:#72777c;padding:0 5px 0 0;vertical-align:middle;top:4px;position:absolute;left:1px}.ep-module .settings-button:after,.ep-module.saving .action-wrap:before{content:' ';width:8px;height:8px;display:inline-block}.ep-module .settings-button:after{border-radius:50%;vertical-align:middle;margin-left:10px;margin-top:-2px}.module-requirements-status-2.ep-module .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid red}.module-requirements-status-2.ep-module .settings .requirements-status-notice{border-color:red}.module-requirements-status-1.ep-module .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid #e3e600}.module-requirements-status-1.ep-module.module-active .postbox .hndle .settings-button:after{background-color:#e3e600}.module-requirements-status-1.ep-module .settings .requirements-status-notice{border-color:#e3e600}.module-requirements-status-0.ep-module .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid #6aa000}.module-requirements-status-0.ep-module.module-active .postbox .hndle .settings-button:after{background-color:#6aa000}.module-requirements-status-0.ep-module .settings .requirements-status-notice{border-color:#6aa000}.ep-module{position:relative;vertical-align:top;margin-bottom:20px}.ep-module.saving .action-wrap:before{vertical-align:middle;margin-right:1.4em;top:4px;border-radius:50%;font-size:7px;position:relative;text-indent:-9999em;border-top:5px solid #ccc;border-right:5px solid #ccc;border-bottom:5px solid #ccc;border-left:5px solid #999;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation:load8 1.1s infinite linear;animation:load8 1.1s infinite linear}.ep-module .description,.ep-module .settings{margin-bottom:0;text-align:left}.ep-module .settings{display:none;overflow:auto}.ep-module .settings h3{margin-top:0;font-size:inherit;font-weight:700}.ep-module .collapse:after,.ep-module .learn-more:after{font-family:dashicons;font-size:1.5em;vertical-align:middle;top:-.07em;position:relative}.ep-module.show-settings .settings{display:block}.ep-module.show-settings .description{display:none}.ep-module.show-settings .settings-button:before{content:"\f142"}.ep-module .settings .requirements-status-notice{border-left:4px solid #6aa000;background-color:#efefef;padding:8px 12px;margin-bottom:10px}.ep-module .settings .action-wrap{clear:both;text-align:right;margin-top:10px;vertical-align:middle}.ep-module .settings .action-wrap a{cursor:pointer;display:inline-block}.ep-module .settings .action-wrap .cancel{margin-top:4px;margin-right:6px}.ep-module .settings .field{clear:both;overflow:auto}.ep-module .settings .field .field-name,.ep-module .settings .field>label{display:block;float:left;margin-right:3em}.ep-module .settings .field .input-wrap{display:block;float:left}.ep-module .settings .field .disabled{color:#c4c4c4}.ep-module .settings .field .disabled input{cursor:default}.ep-module .long{display:none}.ep-module .long p:last-child{margin-bottom:0}.ep-module.show-full .long{display:block}.ep-module .collapse,.ep-module .learn-more{cursor:pointer}.ep-module.show-full .learn-more{display:none}.ep-module .learn-more:after{content:"\f140"}.ep-module .collapse:after{content:"\f142"}.intro h2{padding:9px 15px 4px 0}.wrap.intro{margin-top:30px;margin-bottom:30px;overflow:auto}.wrap.intro .error,.wrap.intro .is-dismissible,.wrap.intro .notice,.wrap.intro .updated{display:none!important}.modules-screenshot{display:none}.setup-message{text-align:center;background-color:#e63e3b;margin:20px -20px 0;padding:2em 0;clear:both}.setup-message .setup-button{border-radius:5px;background-color:#d73c38;color:#f19ea4;display:inline-block;margin:0 .75em;text-decoration:none;padding:.75em 3em;box-shadow:1px 1px 3px 1px rgba(0,0,0,.25)}.setup-message .setup-button-primary{background-color:#fff;color:#d84440}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media (min-width:768px){.intro .left{float:left;width:30%}.modules-screenshot{margin:0 auto;max-width:1000px;width:70%;display:block;height:auto;float:right}.ep-modules .left,.ep-modules .right{display:block;box-sizing:border-box;width:50%}.ep-modules .left{float:left;padding-right:10px}.ep-modules .right{float:right;padding-left:10px}.ep-module .module-message{float:left;padding:.5em 0 0;vertical-align:middle;display:inline-block}} \ No newline at end of file +.ep-header-menu,.wrap .notice,.wrap>h2{z-index:2;position:relative}.wrap h2{color:#888;line-height:1.75;margin:.5em 0 .75em}#wpbody #update-nag,#wpbody .update-nag{margin-left:-20px;width:100%;margin-top:0;margin-bottom:20px}h2.ep-list-features{display:none}.ep-header-menu{background-color:#fff;margin-left:-20px;border-bottom:2px solid #ddd;padding:5px 20px}.ep-header-menu:after{content:' ';clear:both;display:block}.ep-header-menu img{float:left}.ep-header-menu .icons{float:right;padding-right:3px;height:39px;line-height:39px;display:inline-block}.ep-header-menu .icons .dashicons-update{font-size:29px;margin-right:10px;top:-3px}.ep-header-menu .icons a{font-size:27px;vertical-align:middle;color:inherit;position:relative;top:-3px;margin-left:8px;cursor:pointer}.ep-header-menu .icons .dashicons-controls-pause,.ep-header-menu .icons .dashicons-controls-play,.ep-header-menu .icons .dashicons-no{top:1px;font-size:30px}.ep-header-menu .cancel-sync,.ep-header-menu .pause-sync,.ep-header-menu .resume-sync{display:none}.ep-header-menu .sync-status{display:none;color:#666;bottom:-1px;position:relative;margin-right:8px;vertical-align:middle;font-style:italic}.ep-header-menu .progress-bar{margin-bottom:-5px;height:5px;position:absolute;bottom:0;left:0;background-color:#D84440}.ep-features{overflow:auto}.error-overlay.cant-connect,.error-overlay.syncing{content:' ';display:block;position:fixed;top:46px;left:0;right:0;bottom:0;opacity:.6;background-color:#fff;z-index:1}@media (min-width:768px){.error-overlay,.error-overlay.cant-connect,.error-overlay.syncing{top:32px;left:160px}}.ep-feature .postbox{margin-bottom:0}.ep-feature .postbox .hndle{cursor:inherit}.ep-feature .postbox .hndle .settings-button{float:right;display:inline-block;font-size:13px;background-color:#efefef;border-radius:4px;padding:4px 7px 4px 23px;margin-top:-4px;cursor:pointer;color:inherit;position:relative}.ep-feature .settings-button:before{content:"\f140";font:400 19px/1 dashicons;display:inline-block;color:#72777c;padding:0 5px 0 0;vertical-align:middle;top:4px;position:absolute;left:1px}.ep-feature .settings-button:after,.ep-feature.saving .action-wrap:before{content:' ';width:8px;height:8px;display:inline-block}.ep-feature .settings-button:after{border-radius:50%;vertical-align:middle;margin-left:10px;margin-top:-2px}.feature-requirements-status-2.ep-feature .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid red}.feature-requirements-status-2.ep-feature .settings .requirements-status-notice{border-color:red}.feature-requirements-status-1.ep-feature .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid #e3e600}.feature-requirements-status-1.ep-feature.feature-active .postbox .hndle .settings-button:after{background-color:#e3e600}.feature-requirements-status-1.ep-feature .settings .requirements-status-notice{border-color:#e3e600}.feature-requirements-status-0.ep-feature .postbox .hndle .settings-button:after{background-color:transparent;border:1px solid #6aa000}.feature-requirements-status-0.ep-feature.feature-active .postbox .hndle .settings-button:after{background-color:#6aa000}.feature-requirements-status-0.ep-feature .settings .requirements-status-notice{border-color:#6aa000}.ep-feature{position:relative;vertical-align:top;margin-bottom:20px}.ep-feature.saving .action-wrap:before{vertical-align:middle;margin-right:1.4em;top:4px;border-radius:50%;font-size:7px;position:relative;text-indent:-9999em;border-top:5px solid #ccc;border-right:5px solid #ccc;border-bottom:5px solid #ccc;border-left:5px solid #999;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation:load8 1.1s infinite linear;animation:load8 1.1s infinite linear}.ep-feature .description,.ep-feature .settings{margin-bottom:0;text-align:left}.ep-feature .settings{display:none;overflow:auto}.ep-feature .settings h3{margin-top:0;font-size:inherit;font-weight:700}.ep-feature .collapse:after,.ep-feature .learn-more:after{font-family:dashicons;font-size:1.5em;vertical-align:middle;top:-.07em;position:relative}.ep-feature.show-settings .settings{display:block}.ep-feature.show-settings .description{display:none}.ep-feature.show-settings .settings-button:before{content:"\f142"}.ep-feature .settings .requirements-status-notice{border-left:4px solid #6aa000;background-color:#efefef;padding:8px 12px;margin-bottom:10px}.ep-feature .settings .action-wrap{clear:both;text-align:right;margin-top:10px;vertical-align:middle}.ep-feature .settings .action-wrap a{cursor:pointer;display:inline-block}.ep-feature .settings .action-wrap .cancel{margin-top:4px;margin-right:6px}.ep-feature .settings .field{clear:both;overflow:auto}.ep-feature .settings .field .field-name,.ep-feature .settings .field>label{display:block;float:left;margin-right:3em}.ep-feature .settings .field .input-wrap{display:block;float:left}.ep-feature .settings .field .disabled{color:#c4c4c4}.ep-feature .settings .field .disabled input{cursor:default}.ep-feature .long{display:none}.ep-feature .long p:last-child{margin-bottom:0}.ep-feature.show-full .long{display:block}.ep-feature .collapse,.ep-feature .learn-more{cursor:pointer}.ep-feature.show-full .learn-more{display:none}.ep-feature .learn-more:after{content:"\f140"}.ep-feature .collapse:after{content:"\f142"}.intro h2{padding:9px 15px 4px 0}.wrap.intro{margin-top:30px;margin-bottom:30px;overflow:auto}.wrap.intro .error,.wrap.intro .is-dismissible,.wrap.intro .notice,.wrap.intro .updated{display:none!important}.features-screenshot{display:none}.setup-message{text-align:center;background-color:#e63e3b;margin:20px -20px 0;padding:2em 0;clear:both}.setup-message .setup-button{border-radius:5px;background-color:#d73c38;color:#f19ea4;display:inline-block;margin:0 .75em;text-decoration:none;padding:.75em 3em;box-shadow:1px 1px 3px 1px rgba(0,0,0,.25)}.setup-message .setup-button-primary{background-color:#fff;color:#d84440}@-webkit-keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes load8{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media (min-width:768px){.intro .left{float:left;width:30%}.features-screenshot{margin:0 auto;max-width:1000px;width:70%;display:block;height:auto;float:right}.ep-features .left,.ep-features .right{display:block;box-sizing:border-box;width:50%}.ep-features .left{float:left;padding-right:10px}.ep-features .right{float:right;padding-left:10px}.ep-feature .feature-message{float:left;padding:.5em 0 0;vertical-align:middle;display:inline-block}} \ No newline at end of file diff --git a/assets/css/admin.scss b/assets/css/admin.scss index d92098cbc9..779f86fa76 100644 --- a/assets/css/admin.scss +++ b/assets/css/admin.scss @@ -27,7 +27,7 @@ $status-error: #ff0000; margin-bottom: 20px; } -h2.ep-list-modules { +h2.ep-list-features { display: none; // We use this since WP inserts warnings after the first h2 } @@ -114,10 +114,10 @@ h2.ep-list-modules { } /** - * Modules + * Features */ -.ep-modules { +.ep-features { overflow: auto; } @@ -144,15 +144,15 @@ h2.ep-list-modules { } } -.ep-module .postbox { +.ep-feature .postbox { margin-bottom: 0; } -.ep-module .postbox .hndle { +.ep-feature .postbox .hndle { cursor: inherit; } -.ep-module .postbox .hndle .settings-button { +.ep-feature .postbox .hndle .settings-button { float: right; display: inline-block; font-size: 13px; @@ -166,7 +166,7 @@ h2.ep-list-modules { padding-left: 23px; } -.ep-module .settings-button:before { +.ep-feature .settings-button:before { content: "\f140"; font: 400 19px/1 dashicons; display: inline-block; @@ -178,7 +178,7 @@ h2.ep-list-modules { left: 1px; } -.ep-module .settings-button:after { +.ep-feature .settings-button:after { border-radius: 50%; content: ' '; display: inline-block; @@ -189,7 +189,7 @@ h2.ep-list-modules { margin-top: -2px; } -.module-requirements-status-2.ep-module { +.feature-requirements-status-2.ep-feature { .postbox .hndle .settings-button:after { background-color: transparent; border: 1px solid $status-error; @@ -200,13 +200,13 @@ h2.ep-list-modules { } } -.module-requirements-status-1.ep-module { +.feature-requirements-status-1.ep-feature { .postbox .hndle .settings-button:after { background-color: transparent; border: 1px solid $status-warning; } - &.module-active .postbox .hndle .settings-button:after { + &.feature-active .postbox .hndle .settings-button:after { background-color: $status-warning; } @@ -215,13 +215,13 @@ h2.ep-list-modules { } } -.module-requirements-status-0.ep-module { +.feature-requirements-status-0.ep-feature { .postbox .hndle .settings-button:after { background-color: transparent; border: 1px solid $status-ok; } - &.module-active .postbox .hndle .settings-button:after { + &.feature-active .postbox .hndle .settings-button:after { background-color: $status-ok; } @@ -230,13 +230,13 @@ h2.ep-list-modules { } } -.ep-module { +.ep-feature { position: relative; vertical-align: top; margin-bottom: 20px; } -.ep-module.saving .action-wrap:before { +.ep-feature.saving .action-wrap:before { content: ' '; vertical-align: middle; margin-right: 1.4em; @@ -259,13 +259,13 @@ h2.ep-list-modules { animation: load8 1.1s infinite linear; } -.ep-module .description, -.ep-module .settings { +.ep-feature .description, +.ep-feature .settings { margin-bottom: 0; text-align: left; } -.ep-module .settings { +.ep-feature .settings { display: none; overflow: auto; @@ -276,7 +276,7 @@ h2.ep-list-modules { } } -.ep-module.show-settings { +.ep-feature.show-settings { .settings { display: block; @@ -291,14 +291,14 @@ h2.ep-list-modules { } } -.ep-module .settings .requirements-status-notice { +.ep-feature .settings .requirements-status-notice { border-left: 4px solid $status-ok; background-color: #efefef; padding: 8px 12px; margin-bottom: 10px; } -.ep-module .settings .action-wrap { +.ep-feature .settings .action-wrap { clear: both; text-align: right; margin-top: 10px; @@ -315,7 +315,7 @@ h2.ep-list-modules { } } -.ep-module .settings .field { +.ep-feature .settings .field { clear: both; overflow: auto; @@ -340,7 +340,7 @@ h2.ep-list-modules { } } -.ep-module .long { +.ep-feature .long { display: none; p:last-child { @@ -348,20 +348,20 @@ h2.ep-list-modules { } } -.ep-module.show-full .long { +.ep-feature.show-full .long { display: block; } -.ep-module .learn-more, -.ep-module .collapse { +.ep-feature .learn-more, +.ep-feature .collapse { cursor: pointer; } -.ep-module.show-full .learn-more { +.ep-feature.show-full .learn-more { display: none; } -.ep-module .learn-more:after { +.ep-feature .learn-more:after { content: "\f140"; font-family: dashicons; font-size: 1.5em; @@ -370,7 +370,7 @@ h2.ep-list-modules { position: relative; } -.ep-module .collapse:after { +.ep-feature .collapse:after { content: "\f142"; font-family: dashicons; font-size: 1.5em; @@ -401,7 +401,7 @@ h2.ep-list-modules { } } -.modules-screenshot { +.features-screenshot { display: none; } @@ -461,7 +461,7 @@ h2.ep-list-modules { width: 30%; } - .modules-screenshot { + .features-screenshot { margin: 0 auto; max-width: 1000px; width: 70%; @@ -470,7 +470,7 @@ h2.ep-list-modules { float: right; } - .ep-modules .left { + .ep-features .left { display: block; float: left; box-sizing: border-box; @@ -478,7 +478,7 @@ h2.ep-list-modules { width: 50%; } - .ep-modules .right { + .ep-features .right { display: block; float: right; box-sizing: border-box; @@ -486,7 +486,7 @@ h2.ep-list-modules { width: 50%; } - .ep-module .module-message { + .ep-feature .feature-message { float: left; padding: 0; vertical-align: middle; diff --git a/assets/js/admin.js b/assets/js/admin.js index 2b2c6a78ca..9df19abd05 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -1,5 +1,5 @@ ( function( $ ) { - var $modules = $( document.getElementsByClassName( 'ep-modules' ) ); + var $features = $( document.getElementsByClassName( 'ep-features' ) ); var $errorOverlay = $( document.getElementsByClassName( 'error-overlay' ) ); var $progressBar = $(document.getElementsByClassName( 'progress-bar' ) ); @@ -10,31 +10,31 @@ var $cancelSyncButton = $(document.getElementsByClassName( 'cancel-sync' ) ); var syncStatus = 'sync'; - var moduleSync = false; + var featureSync = false; var currentSite; var siteStack; var processed = 0; var toProcess = 0; - $modules.on( 'click', '.learn-more, .collapse', function( event ) { - $module = $( this ).parents( '.ep-module' ); - $module.toggleClass( 'show-full' ); + $features.on( 'click', '.learn-more, .collapse', function( event ) { + $feature = $( this ).parents( '.ep-feature' ); + $feature.toggleClass( 'show-full' ); } ); - $modules.on( 'click', '.settings-button', function( event ) { - $module = $( this ).parents( '.ep-module' ); - $module.toggleClass( 'show-settings' ); + $features.on( 'click', '.settings-button', function( event ) { + $feature = $( this ).parents( '.ep-feature' ); + $feature.toggleClass( 'show-settings' ); } ); - $modules.on( 'click', '.save-settings', function( event ) { + $features.on( 'click', '.save-settings', function( event ) { event.preventDefault(); - var module = event.target.getAttribute( 'data-module' ); - var $module = $modules.find( '.ep-module-' + module ); + var feature = event.target.getAttribute( 'data-feature' ); + var $feature = $features.find( '.ep-feature-' + feature ); var settings = {}; - var $settings = $module.find('.setting-field'); + var $settings = $feature.find('.setting-field'); $settings.each(function() { var type = $( this ).attr( 'type' ); @@ -48,42 +48,42 @@ } }); - $module.addClass( 'saving' ); + $feature.addClass( 'saving' ); $.ajax( { method: 'post', url: ajaxurl, data: { - action: 'ep_save_module', - module: module, + action: 'ep_save_feature', + feature: feature, nonce: ep.nonce, settings: settings } } ).done( function( response ) { setTimeout( function() { - $module.removeClass( 'saving' ); + $feature.removeClass( 'saving' ); if ( '1' === settings.active ) { - $module.addClass( 'module-active' ); + $feature.addClass( 'feature-active' ); } else { - $module.removeClass( 'module-active' ); + $feature.removeClass( 'feature-active' ); } if ( response.data.reindex ) { syncStatus = 'sync'; - $module.addClass( 'module-syncing' ); + $feature.addClass( 'feature-syncing' ); - moduleSync = module; + featureSync = feature; sync(); } }, 700 ); } ).error( function() { setTimeout( function() { - $module.removeClass( 'saving' ); - $module.removeClass( 'module-active' ); - $module.removeClass( 'module-syncing' ); + $feature.removeClass( 'saving' ); + $feature.removeClass( 'feature-active' ); + $feature.removeClass( 'feature-syncing' ); }, 700 ); } ); } ); @@ -96,8 +96,8 @@ processed = ep.index_meta.offset; toProcess = ep.index_meta['found_posts']; - if ( ep.index_meta.module_sync ) { - moduleSync = ep.index_meta.module_sync; + if ( ep.index_meta.feature_sync ) { + featureSync = ep.index_meta.feature_sync; } if ( ep.index_meta.current_site ) { @@ -203,12 +203,12 @@ $errorOverlay.removeClass( 'syncing' ); $progressBar.hide(); - if ( moduleSync ) { - var $module = $modules.find( '.ep-module-' + moduleSync ); - $module.removeClass( 'module-syncing' ); + if ( featureSync ) { + var $feature = $features.find( '.ep-feature-' + featureSync ); + $feature.removeClass( 'feature-syncing' ); } - moduleSync = null; + featureSync = null; setTimeout( function() { $syncStatusText.hide(); @@ -223,12 +223,12 @@ $resumeSyncButton.hide(); $startSyncButton.show(); - if ( moduleSync ) { - var $module = $modules.find( '.ep-module-' + moduleSync ); - $module.removeClass( 'module-syncing' ); + if ( featureSync ) { + var $feature = $features.find( '.ep-feature-' + featureSync ); + $feature.removeClass( 'feature-syncing' ); } - moduleSync = null; + featureSync = null; } else if ( 'finished' === syncStatus ) { $syncStatusText.text( ep.sync_complete ); @@ -240,12 +240,12 @@ $startSyncButton.show(); $errorOverlay.removeClass( 'syncing' ); - if ( moduleSync ) { - var $module = $modules.find( '.ep-module-' + moduleSync ); - $module.removeClass( 'module-syncing' ); + if ( featureSync ) { + var $feature = $features.find( '.ep-feature-' + featureSync ); + $feature.removeClass( 'feature-syncing' ); } - moduleSync = null; + featureSync = null; setTimeout( function() { $syncStatusText.hide(); @@ -270,7 +270,7 @@ url: ajaxurl, data: { action: 'ep_index', - module_sync: moduleSync, + feature_sync: featureSync, nonce: ep.nonce } } ).done( function( response ) { diff --git a/assets/js/admin.min.js b/assets/js/admin.min.js index 875cc35baa..1915eb8e84 100644 --- a/assets/js/admin.min.js +++ b/assets/js/admin.min.js @@ -1 +1 @@ -!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=ep.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=ep.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=ep.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(ep.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null}else if("finished"===o){if(j.text(ep.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-module-"+p);c.removeClass("module-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:ep.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",module_sync:p,nonce:ep.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-modules")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$module=a(this).parents(".ep-module"),$module.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$module=a(this).parents(".ep-module"),$module.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-module"),e=g.find(".ep-module-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_module",module:c,nonce:ep.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("module-active"):e.removeClass("module-active"),a.data.reindex&&(o="sync",e.addClass("module-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("module-active"),e.removeClass("module-syncing")},700)})}),ep.index_meta&&(ep.index_meta.wpcli?(o="wpcli",b()):(q=ep.index_meta.offset,r=ep.index_meta.found_posts,ep.index_meta.module_sync&&(p=ep.index_meta.module_sync),ep.index_meta.current_site&&(e=ep.index_meta.current_site),ep.index_meta.site_stack&&(f=ep.index_meta.site_stack),f&&f.length?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):0!==r||ep.index_meta.start?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):(o="finished",b()))),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file +!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=ep.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=ep.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=ep.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(ep.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null}else if("finished"===o){if(j.text(ep.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:ep.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",feature_sync:p,nonce:ep.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-features")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-feature"),e=g.find(".ep-feature-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_feature",feature:c,nonce:ep.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("feature-active"):e.removeClass("feature-active"),a.data.reindex&&(o="sync",e.addClass("feature-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("feature-active"),e.removeClass("feature-syncing")},700)})}),ep.index_meta&&(ep.index_meta.wpcli?(o="wpcli",b()):(q=ep.index_meta.offset,r=ep.index_meta.found_posts,ep.index_meta.feature_sync&&(p=ep.index_meta.feature_sync),ep.index_meta.current_site&&(e=ep.index_meta.current_site),ep.index_meta.site_stack&&(f=ep.index_meta.site_stack),f&&f.length?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):0!==r||ep.index_meta.start?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):(o="finished",b()))),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file diff --git a/bin/wp-cli.php b/bin/wp-cli.php index e1661e44b1..e0b4ad90f4 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -31,117 +31,117 @@ class ElasticPress_CLI_Command extends WP_CLI_Command { private $failed_posts_message = array(); /** - * Activate a module. + * Activate a feature. * - * @synopsis [--network-wide] - * @subcommand activate-module + * @synopsis [--network-wide] + * @subcommand activate-feature * @since 2.1 * @param array $args * @param array $assoc_args */ - public function activate_module( $args, $assoc_args ) { - $module = ep_get_registered_module( $args[0] ); + public function activate_feature( $args, $assoc_args ) { + $feature = ep_get_registered_feature( $args[0] ); - if ( empty( $module ) ) { - WP_CLI::error( __( 'No module with that slug is registered', 'elasticpress' ) ); + if ( empty( $feature ) ) { + WP_CLI::error( __( 'No feature with that slug is registered', 'elasticpress' ) ); } if ( ! empty( $assoc_args['network-wide'] ) ) { - $active_modules = get_site_option( 'ep_active_modules', array() ); + $active_features = get_site_option( 'ep_active_features', array() ); } else { - $active_modules = get_option( 'ep_active_modules', array() ); + $active_features = get_option( 'ep_active_features', array() ); } - if ( $module->is_active() ) { - WP_CLI::error( __( 'This module is already active', 'elasticpress' ) ); + if ( $feature->is_active() ) { + WP_CLI::error( __( 'This feature is already active', 'elasticpress' ) ); } - if ( is_wp_error( $module->dependencies_met() ) ) { - WP_CLI::error( __( 'Module depedencies are not met', 'elasticpress' ) ); + if ( is_wp_error( $feature->dependencies_met() ) ) { + WP_CLI::error( __( 'Feature depedencies are not met', 'elasticpress' ) ); } - $active_modules[] = $module->slug; + $active_features[] = $feature->slug; - $module->post_activation(); + $feature->post_activation(); - if ( $module->requires_install_reindex ) { - WP_CLI::warning( __( 'This module requires a re-index. You may want to run the index command next.', 'elasticpress' ) ); + if ( $feature->requires_install_reindex ) { + WP_CLI::warning( __( 'This feature requires a re-index. You may want to run the index command next.', 'elasticpress' ) ); } if ( ! empty( $assoc_args['network-wide'] ) ) { - update_site_option( 'ep_active_modules', $active_modules ); + update_site_option( 'ep_active_features', $active_features ); } else { - update_option( 'ep_active_modules', $active_modules ); + update_option( 'ep_active_features', $active_features ); } - WP_CLI::success( __( 'Module activated', 'elasticpress' ) ); + WP_CLI::success( __( 'Feature activated', 'elasticpress' ) ); } /** - * Dectivate a module. + * Dectivate a feature. * - * @synopsis [--network-wide] - * @subcommand deactivate-module + * @synopsis [--network-wide] + * @subcommand deactivate-feature * @since 2.1 * @param array $args * @param array $assoc_args */ - public function deactivate_module( $args, $assoc_args ) { - $module = ep_get_registered_module( $args[0] ); + public function deactivate_feature( $args, $assoc_args ) { + $feature = ep_get_registered_feature( $args[0] ); - if ( empty( $module ) ) { - WP_CLI::error( __( 'No module with that slug is registered', 'elasticpress' ) ); + if ( empty( $feature ) ) { + WP_CLI::error( __( 'No feature with that slug is registered', 'elasticpress' ) ); } if ( ! empty( $assoc_args['network-wide'] ) ) { - $active_modules = get_site_option( 'ep_active_modules', array() ); + $active_features = get_site_option( 'ep_active_features', array() ); } else { - $active_modules = get_option( 'ep_active_modules', array() ); + $active_features = get_option( 'ep_active_features', array() ); } - $key = array_search( $module->slug, $active_modules ); + $key = array_search( $feature->slug, $active_features ); if ( false !== $key ) { - unset( $active_modules[$key] ); + unset( $active_features[$key] ); } else { - WP_CLI::error( __( 'Module is not active', 'elasticpress' ) ); + WP_CLI::error( __( 'Feature is not active', 'elasticpress' ) ); } if ( ! empty( $assoc_args['network-wide'] ) ) { - update_site_option( 'ep_active_modules', $active_modules ); + update_site_option( 'ep_active_features', $active_features ); } else { - update_option( 'ep_active_modules', $active_modules ); + update_option( 'ep_active_features', $active_features ); } - WP_CLI::success( __( 'Module deactivated', 'elasticpress' ) ); + WP_CLI::success( __( 'Feature deactivated', 'elasticpress' ) ); } /** - * List modules (either active or all) + * List features (either active or all) * * @synopsis [--all] [--network-wide] - * @subcommand list-modules + * @subcommand list-features * @since 2.1 * @param array $args * @param array $assoc_args */ - public function list_modules( $args, $assoc_args ) { + public function list_features( $args, $assoc_args ) { if ( empty( $assoc_args['all'] ) ) { if ( ! empty( $assoc_args['network-wide'] ) ) { - $modules = get_site_option( 'ep_active_modules', array() ); + $features = get_site_option( 'ep_active_features', array() ); } else { - $modules = get_option( 'ep_active_modules', array() ); + $features = get_option( 'ep_active_features', array() ); } - WP_CLI::line( __( 'Active modules:', 'elasticpress' ) ); + WP_CLI::line( __( 'Active features:', 'elasticpress' ) ); } else { - WP_CLI::line( __( 'Registered modules:', 'elasticpress' ) ); - $modules = wp_list_pluck( EP_Modules::factory()->registered_modules, 'slug' ); + WP_CLI::line( __( 'Registered features:', 'elasticpress' ) ); + $features = wp_list_pluck( EP_Features::factory()->registered_features, 'slug' ); } - foreach ( $modules as $module ) { - WP_CLI::line( $module ); + foreach ( $features as $feature ) { + WP_CLI::line( $feature ); } } @@ -804,4 +804,4 @@ private function _connect_check() { WP_CLI::error( __( 'Unable to reach Elasticsearch Server! Check that service is running.', 'elasticpress' ) ); } } -} \ No newline at end of file +} diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 27239d50b6..149f744a9a 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -36,7 +36,7 @@ public function setup() { add_action( 'admin_menu', array( $this, 'action_admin_menu' ) ); } - add_action( 'wp_ajax_ep_save_module', array( $this, 'action_wp_ajax_ep_save_module' ) ); + add_action( 'wp_ajax_ep_save_feature', array( $this, 'action_wp_ajax_ep_save_feature' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); add_action( 'admin_init', array( $this, 'action_admin_init' ) ); add_action( 'admin_init', array( $this, 'intro_or_dashboard' ) ); @@ -218,8 +218,8 @@ public function action_wp_ajax_ep_index() { } } - if ( ! empty( $_POST['module_sync'] ) ) { - $index_meta['module_sync'] = esc_attr( $_POST['module_sync'] ); + if ( ! empty( $_POST['feature_sync'] ) ) { + $index_meta['feature_sync'] = esc_attr( $_POST['feature_sync'] ); } } else if ( ! empty( $index_meta['site_stack'] ) && $index_meta['offset'] >= $index_meta['found_posts'] ) { $status = 'start'; @@ -379,41 +379,41 @@ public function action_wp_ajax_ep_cancel_index() { } /** - * Save individual module settings + * Save individual feature settings * * @since 2.2 */ - public function action_wp_ajax_ep_save_module() { - if ( empty( $_POST['module'] ) || empty( $_POST['settings'] ) || ! check_ajax_referer( 'ep_nonce', 'nonce', false ) ) { + public function action_wp_ajax_ep_save_feature() { + if ( empty( $_POST['feature'] ) || empty( $_POST['settings'] ) || ! check_ajax_referer( 'ep_nonce', 'nonce', false ) ) { wp_send_json_error(); exit; } - $module = ep_get_registered_module( $_POST['module'] ); - $original_state = $module->is_active(); + $feature = ep_get_registered_feature( $_POST['feature'] ); + $original_state = $feature->is_active(); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $module_settings = get_site_option( 'ep_module_settings', array() ); + $feature_settings = get_site_option( 'ep_feature_settings', array() ); } else { - $module_settings = get_option( 'ep_module_settings', array() ); + $feature_settings = get_option( 'ep_feature_settings', array() ); } - $module_settings[ $_POST['module'] ] = wp_parse_args( $_POST['settings'], $module->default_settings ); - $module_settings[ $_POST['module'] ]['active'] = (bool) $module_settings[ $_POST['module'] ]['active']; + $feature_settings[ $_POST['feature'] ] = wp_parse_args( $_POST['settings'], $feature->default_settings ); + $feature_settings[ $_POST['feature'] ]['active'] = (bool) $feature_settings[ $_POST['feature'] ]['active']; - $sanitize_module_settings = apply_filters( 'ep_sanitize_module_settings', $module_settings, $module ); + $sanitize_feature_settings = apply_filters( 'ep_sanitize_feature_settings', $feature_settings, $feature ); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_module_settings', $sanitize_module_settings ); + update_site_option( 'ep_feature_settings', $sanitize_feature_settings ); } else { - update_option( 'ep_module_settings', $sanitize_module_settings ); + update_option( 'ep_feature_settings', $sanitize_feature_settings ); } $data = array( 'reindex' => false, ); - if ( $module_settings[ $_POST['module'] ]['active'] && ! $original_state ) { + if ( $feature_settings[ $_POST['feature'] ]['active'] && ! $original_state ) { $data['reindex'] = true; } diff --git a/classes/class-ep-feature.php b/classes/class-ep-feature.php new file mode 100644 index 0000000000..ab47555e41 --- /dev/null +++ b/classes/class-ep-feature.php @@ -0,0 +1,277 @@ +code = $code; + + $this->message = $message; + } + + /** + * Returns the status of a feature + * + * 1 is no issues (hollow green) + * 2 is usable but there are warnngs (hollow yellow) + * 3 is not usable (hollow red) + * + * @var int + * @since 2.2 + */ + public $code; + + /** + * Optional message to describe status code + * + * @var string + * @since 2.2 + */ + public $message; +} + +class EP_Feature { + /** + * Feature slug + * + * @var string + * @since 2.1 + */ + public $slug; + + /** + * Feature pretty title + * + * @var string + * @since 2.1 + */ + public $title; + + /** + * Optional feature default settings + * + * @since 2.2 + * @var array + */ + public $default_settings = array(); + + /** + * Contains registered callback to execute after setup + * + * @since 2.1 + * @var callback + */ + public $setup_cb; + + /** + * Contains registered callback to output feature summary in feature box + * + * @since 2.1 + * @var callback + */ + public $feature_box_summary_cb; + + /** + * Contains registered callback to output feature long description in feature box + * + * @since 2.1 + * @var callback + */ + public $feature_box_long_cb; + + /** + * Output optional extra settings fields + * + * @since 2.2 + * @var callback + */ + public $feature_box_settings_cb; + + /** + * Contains registered callback to execute after activation + * + * @since 2.1 + * @var callback + */ + public $post_activation_cb; + + /** + * True if the feature requires content reindexing after activating + * + * @since 2.1 + * @var [type] + */ + public $requires_install_reindex; + + /** + * Initiate the feature, setting all relevant instance variables + * + * @since 2.1 + */ + public function __construct( $args ) { + foreach ( $args as $key => $value ) { + $this->$key = $value; + } + + do_action( 'ep_feature_create', $this ); + } + + /** + * Run on every page load for feature to set itself up + * + * @since 2.1 + */ + public function setup() { + if ( ! empty( $this->setup_cb ) ) { + call_user_func( $this->setup_cb, $this ); + } + + do_action( 'ep_feature_setup', $this->slug, $this ); + } + + /** + * Returns requirements status of feature + * + * @since 2.2 + * @return EP_Feature_Requirements_Status + */ + public function requirements_status() { + $status = new EP_Feature_Requirements_Status( 0 ); + + if ( ! empty( $this->requirements_status_cb ) ) { + $status = call_user_func( $this->requirements_status_cb, $status, $this ); + } + + return apply_filters( 'ep_feature_requirements_status', $status, $this ); + } + + /** + * Returns true if feature is active + * + * @since 2.2 + * @return boolean + */ + public function is_active() { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $feature_settings = get_site_option( 'ep_feature_settings', array() ); + } else { + $feature_settings = get_option( 'ep_feature_settings', array() ); + } + + $active = false; + + if ( ! empty( $feature_settings[ $this->slug ] ) && $feature_settings[ $this->slug ]['active'] ) { + $active = true; + } + + return apply_filters( 'ep_feature_active', $active, $feature_settings, $this ); + } + + /** + * Ran after a feature is activated + * + * @since 2.1 + */ + public function post_activation() { + if ( ! empty( $this->post_activation_cb ) ) { + call_user_func( $this->post_activation_cb, $this ); + } + + do_action( 'ep_feature_post_activation', $this->slug, $this ); + } + + /** + * Outputs feature box + * + * @since 2.1 + */ + public function output_feature_box() { + if ( ! empty( $this->feature_box_summary_cb ) ) { + call_user_func( $this->feature_box_summary_cb, $this ); + } + + do_action( 'ep_feature_box_summary', $this->slug, $this ); + + if ( ! empty( $this->feature_box_long_cb ) ) { + ?> + + + +

+ feature_box_long_cb, $this ); ?> + +

+ slug, $this ); ?> + +
+ feature_box_full_cb ) ) { + call_user_func( $this->feature_box_full_cb, $this ); + } + + do_action( 'ep_feature_box_full', $this->slug, $this ); + } + + public function output_settings_box() { + $requirements_status = $this->requirements_status(); + ?> + + message ) ) : ?> +
+ message ); ?> +
+ + +

+ +
+
+
+
+ +
+
+ + feature_box_settings_cb ) ) { + call_user_func( $this->feature_box_settings_cb, $this ); + return; + } + do_action( 'ep_feature_box_settings', $this->slug, $this ); + ?> + +
+ +
+ registered_features[$slug] = new EP_Feature( $feature_args ); + + return true; + } + + /** + * Set up all active features + * + * @since 2.1 + */ + public function setup_features() { + foreach ( $this->registered_features as $feature_slug => $feature ) { + if ( $feature->is_active() ) { + $feature->setup(); + } + } + } + + /** + * Return singleton instance of class + * + * @return object + * @since 2.1 + */ + public static function factory() { + static $instance = false; + + if ( ! $instance ) { + $instance = new self(); + $instance->setup(); + } + + return $instance; + } +} + +EP_Features::factory(); + +/** + * Main function for registering new feature. Since comment above for details + * + * @param string $slug + * @param array $feature_args + * @since 2.1 + * @return bool + */ +function ep_register_feature( $slug, $feature_args ) { + return EP_Features::factory()->register_feature( $slug, $feature_args ); +} + +/** + * Easy access function to get a EP_Feature object from a slug + * @param string $slug + * @since 2.1 + * @return EP_Feature + */ +function ep_get_registered_feature( $slug ) { + if ( empty( EP_Features::factory()->registered_features[$slug] ) ) { + return false; + } + return EP_Features::factory()->registered_features[$slug]; +} diff --git a/classes/class-ep-module.php b/classes/class-ep-module.php deleted file mode 100644 index 9e250abc2e..0000000000 --- a/classes/class-ep-module.php +++ /dev/null @@ -1,277 +0,0 @@ -code = $code; - - $this->message = $message; - } - - /** - * Returns the status of a module - * - * 1 is no issues (hollow green) - * 2 is usable but there are warnngs (hollow yellow) - * 3 is not usable (hollow red) - * - * @var int - * @since 2.2 - */ - public $code; - - /** - * Optional message to describe status code - * - * @var string - * @since 2.2 - */ - public $message; -} - -class EP_Module { - /** - * Module slug - * - * @var string - * @since 2.1 - */ - public $slug; - - /** - * Module pretty title - * - * @var string - * @since 2.1 - */ - public $title; - - /** - * Optional module default settings - * - * @since 2.2 - * @var array - */ - public $default_settings = array(); - - /** - * Contains registered callback to execute after setup - * - * @since 2.1 - * @var callback - */ - public $setup_cb; - - /** - * Contains registered callback to output module summary in module box - * - * @since 2.1 - * @var callback - */ - public $module_box_summary_cb; - - /** - * Contains registered callback to output module long description in module box - * - * @since 2.1 - * @var callback - */ - public $module_box_long_cb; - - /** - * Output optional extra settings fields - * - * @since 2.2 - * @var callback - */ - public $module_box_settings_cb; - - /** - * Contains registered callback to execute after activation - * - * @since 2.1 - * @var callback - */ - public $post_activation_cb; - - /** - * True if the module requires content reindexing after activating - * - * @since 2.1 - * @var [type] - */ - public $requires_install_reindex; - - /** - * Initiate the module, setting all relevant instance variables - * - * @since 2.1 - */ - public function __construct( $args ) { - foreach ( $args as $key => $value ) { - $this->$key = $value; - } - - do_action( 'ep_module_create', $this ); - } - - /** - * Run on every page load for module to set itself up - * - * @since 2.1 - */ - public function setup() { - if ( ! empty( $this->setup_cb ) ) { - call_user_func( $this->setup_cb, $this ); - } - - do_action( 'ep_module_setup', $this->slug, $this ); - } - - /** - * Returns requirements status of module - * - * @since 2.2 - * @return EP_Module_Requirements_Status - */ - public function requirements_status() { - $status = new EP_Module_Requirements_Status( 0 ); - - if ( ! empty( $this->requirements_status_cb ) ) { - $status = call_user_func( $this->requirements_status_cb, $status, $this ); - } - - return apply_filters( 'ep_module_requirements_status', $status, $this ); - } - - /** - * Returns true if module is active - * - * @since 2.2 - * @return boolean - */ - public function is_active() { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $module_settings = get_site_option( 'ep_module_settings', array() ); - } else { - $module_settings = get_option( 'ep_module_settings', array() ); - } - - $active = false; - - if ( ! empty( $module_settings[ $this->slug ] ) && $module_settings[ $this->slug ]['active'] ) { - $active = true; - } - - return apply_filters( 'ep_module_active', $active, $module_settings, $this ); - } - - /** - * Ran after a module is activated - * - * @since 2.1 - */ - public function post_activation() { - if ( ! empty( $this->post_activation_cb ) ) { - call_user_func( $this->post_activation_cb, $this ); - } - - do_action( 'ep_module_post_activation', $this->slug, $this ); - } - - /** - * Outputs module box - * - * @since 2.1 - */ - public function output_module_box() { - if ( ! empty( $this->module_box_summary_cb ) ) { - call_user_func( $this->module_box_summary_cb, $this ); - } - - do_action( 'ep_module_box_summary', $this->slug, $this ); - - if ( ! empty( $this->module_box_long_cb ) ) { - ?> - - - -
- module_box_long_cb, $this ); ?> - -

- slug, $this ); ?> - -
- module_box_full_cb ) ) { - call_user_func( $this->module_box_full_cb, $this ); - } - - do_action( 'ep_module_box_full', $this->slug, $this ); - } - - public function output_settings_box() { - $requirements_status = $this->requirements_status(); - ?> - - message ) ) : ?> -
- message ); ?> -
- - -

- -
-
-
-
- -
-
- - module_box_settings_cb ) ) { - call_user_func( $this->module_box_settings_cb, $this ); - return; - } - do_action( 'ep_module_box_settings', $this->slug, $this ); - ?> - -
- -
- registered_modules[$slug] = new EP_Module( $module_args ); - - return true; - } - - /** - * Set up all active modules - * - * @since 2.1 - */ - public function setup_modules() { - foreach ( $this->registered_modules as $module_slug => $module ) { - if ( $module->is_active() ) { - $module->setup(); - } - } - } - - /** - * Return singleton instance of class - * - * @return object - * @since 2.1 - */ - public static function factory() { - static $instance = false; - - if ( ! $instance ) { - $instance = new self(); - $instance->setup(); - } - - return $instance; - } -} - -EP_Modules::factory(); - -/** - * Main function for registering new module. Since comment above for details - * - * @param string $slug - * @param array $module_args - * @since 2.1 - * @return bool - */ -function ep_register_module( $slug, $module_args ) { - return EP_Modules::factory()->register_module( $slug, $module_args ); -} - -/** - * Easy access function to get a EP_Module object from a slug - * @param string $slug - * @since 2.1 - * @return EP_Module - */ -function ep_get_registered_module( $slug ) { - if ( empty( EP_Modules::factory()->registered_modules[$slug] ) ) { - return false; - } - return EP_Modules::factory()->registered_modules[$slug]; -} diff --git a/elasticpress.php b/elasticpress.php index f599276594..a83708f263 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -23,7 +23,6 @@ define( 'EP_URL', plugin_dir_url( __FILE__ ) ); define( 'EP_PATH', plugin_dir_path( __FILE__ ) ); define( 'EP_VERSION', '2.2' ); -define( 'EP_MODULES_DIR', dirname( __FILE__ ) . '/modules' ); require_once( 'classes/class-ep-config.php' ); require_once( 'classes/class-ep-api.php' ); @@ -38,15 +37,15 @@ require_once( 'classes/class-ep-sync-manager.php' ); require_once( 'classes/class-ep-wp-query-integration.php' ); require_once( 'classes/class-ep-wp-date-query.php' ); -require_once( 'classes/class-ep-module.php' ); -require_once( 'classes/class-ep-modules.php' ); +require_once( 'classes/class-ep-feature.php' ); +require_once( 'classes/class-ep-features.php' ); require_once( 'classes/class-ep-dashboard.php' ); -// Include core modules -require_once( 'modules/search/search.php' ); -require_once( 'modules/related-posts/related-posts.php' ); -require_once( 'modules/admin/admin.php' ); -require_once( 'modules/woocommerce/woocommerce.php' ); +// Include core features +require_once( 'features/search/search.php' ); +require_once( 'features/related-posts/related-posts.php' ); +require_once( 'features/admin/admin.php' ); +require_once( 'features/woocommerce/woocommerce.php' ); /** * WP CLI Commands @@ -56,35 +55,35 @@ } /** - * On activate, all modules that meet their requirements with no warnings should be activated. + * On activate, all features that meet their requirements with no warnings should be activated. * * @since 2.1 */ function ep_on_activate() { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $module_settings = get_site_option( 'ep_module_settings', false ); + $feature_settings = get_site_option( 'ep_feature_settings', false ); } else { - $module_settings = get_option( 'ep_module_settings', false ); + $feature_settings = get_option( 'ep_feature_settings', false ); } - if ( false === $module_settings ) { - $registered_modules = EP_Modules::factory()->registered_modules; + if ( false === $feature_settings ) { + $registered_features = EP_Features::factory()->registered_features; - foreach ( $registered_modules as $slug => $module ) { - if ( 0 === $module->requirements_status()->code ) { - $module_settings[ $slug ] = ( ! empty( $module->default_settings ) ) ? $module->default_settings : array(); - $module_settings[ $slug ]['active'] = true; + foreach ( $registered_features as $slug => $feature ) { + if ( 0 === $feature->requirements_status()->code ) { + $feature_settings[ $slug ] = ( ! empty( $feature->default_settings ) ) ? $feature->default_settings : array(); + $feature_settings[ $slug ]['active'] = true; - $module->post_activation(); + $feature->post_activation(); } } } if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_module_settings', $module_settings ); + update_site_option( 'ep_feature_settings', $feature_settings ); delete_site_option( 'ep_index_meta' ); } else { - update_option( 'ep_module_settings', $module_settings ); + update_option( 'ep_feature_settings', $feature_settings ); delete_option( 'ep_index_meta' ); } } diff --git a/modules/admin/admin.php b/features/admin/admin.php similarity index 83% rename from modules/admin/admin.php rename to features/admin/admin.php index cea18db223..515c974f3b 100644 --- a/modules/admin/admin.php +++ b/features/admin/admin.php @@ -1,6 +1,6 @@

@@ -112,11 +112,11 @@ function ep_admin_get_statuses( $statuses ) { } /** - * Determine WC module reqs status + * Determine WC feature reqs status * - * @param EP_Module_Requirements_Status $status + * @param EP_Feature_Requirements_Status $status * @since 2.2 - * @return EP_Module_Requirements_Status + * @return EP_Feature_Requirements_Status */ function ep_admin_requirements_status( $status ) { $host = ep_get_host(); @@ -130,14 +130,14 @@ function ep_admin_requirements_status( $status ) { } /** - * Register the module + * Register the feature */ -ep_register_module( 'admin', array( +ep_register_feature( 'admin', array( 'title' => 'Admin', 'setup_cb' => 'ep_admin_setup', 'requirements_status_cb' => 'ep_admin_requirements_status', - 'module_box_summary_cb' => 'ep_admin_module_box_summary', - 'module_box_long_cb' => 'ep_admin_module_box_long', + 'feature_box_summary_cb' => 'ep_admin_feature_box_summary', + 'feature_box_long_cb' => 'ep_admin_feature_box_long', 'requires_install_reindex' => true, ) ); diff --git a/modules/related-posts/related-posts.php b/features/related-posts/related-posts.php similarity index 79% rename from modules/related-posts/related-posts.php rename to features/related-posts/related-posts.php index ca03237071..ad77afc72f 100644 --- a/modules/related-posts/related-posts.php +++ b/features/related-posts/related-posts.php @@ -1,6 +1,6 @@

-

+

'Related Posts', 'setup_cb' => 'ep_related_posts_setup', - 'module_box_summary_cb' => 'ep_related_posts_module_box_summary', - 'module_box_long_cb' => 'ep_related_posts_module_box_long', + 'feature_box_summary_cb' => 'ep_related_posts_feature_box_summary', + 'feature_box_long_cb' => 'ep_related_posts_feature_box_long', 'requires_install_reindex' => false, ) ); diff --git a/modules/related-posts/widget.php b/features/related-posts/widget.php similarity index 100% rename from modules/related-posts/widget.php rename to features/related-posts/widget.php diff --git a/modules/search/search.php b/features/search/search.php similarity index 87% rename from modules/search/search.php rename to features/search/search.php index 64d9e7b1cf..2d11a6bab6 100644 --- a/modules/search/search.php +++ b/features/search/search.php @@ -1,40 +1,40 @@

- +

'Search', 'setup_cb' => 'ep_search_setup', - 'module_box_summary_cb' => 'ep_search_module_box_summary', - 'module_box_long_cb' => 'ep_search_module_box_long', + 'feature_box_summary_cb' => 'ep_search_feature_box_summary', + 'feature_box_long_cb' => 'ep_search_feature_box_long', 'requires_install_reindex' => false, ) ); diff --git a/modules/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php similarity index 95% rename from modules/woocommerce/woocommerce.php rename to features/woocommerce/woocommerce.php index c6bb875033..2ee3e89abd 100644 --- a/modules/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -1,6 +1,6 @@ -

+

@@ -548,11 +548,11 @@ function ep_wc_module_box_long() { } /** - * Determine WC module reqs status + * Determine WC feature reqs status * - * @param EP_Module_Requirements_Status $status + * @param EP_Feature_Requirements_Status $status * @since 2.2 - * @return EP_Module_Requirements_Status + * @return EP_Feature_Requirements_Status */ function ep_wc_requirements_status( $status ) { if ( ! class_exists( 'WooCommerce' ) ) { @@ -564,14 +564,14 @@ function ep_wc_requirements_status( $status ) { } /** - * Register the module + * Register the feature */ -ep_register_module( 'woocommerce', array( +ep_register_feature( 'woocommerce', array( 'title' => 'WooCommerce', 'setup_cb' => 'ep_wc_setup', 'requirements_status_cb' => 'ep_wc_requirements_status', - 'module_box_summary_cb' => 'ep_wc_module_box_summary', - 'module_box_long_cb' => 'ep_wc_module_box_long', + 'feature_box_summary_cb' => 'ep_wc_feature_box_summary', + 'feature_box_long_cb' => 'ep_wc_feature_box_long', 'requires_install_reindex' => true, 'dependencies_met_cb' => 'wc_dependencies_met_cb', ) ); diff --git a/includes/dashboard-page.php b/includes/dashboard-page.php index e972935a77..124a849b4c 100644 --- a/includes/dashboard-page.php +++ b/includes/dashboard-page.php @@ -21,46 +21,46 @@
-

-
- registered_modules; ?> +

+
+ registered_features; ?> requirements_status(); - $active = $module->is_active(); + $requirements_status = $feature->requirements_status(); + $active = $feature->is_active(); - $module_classes = 'module-requirements-status-' . (int) $requirements_status->code; + $feature_classes = 'feature-requirements-status-' . (int) $requirements_status->code; if ( ! empty( $active ) ) { - $module_classes .= ' module-active'; + $feature_classes .= ' feature-active'; } - if ( ! empty( $index_meta ) && ! empty( $index_meta['module_sync'] ) && $module->slug === $index_meta['module_sync'] ) { - $module_classes .= ' module-syncing'; + if ( ! empty( $index_meta ) && ! empty( $index_meta['feature_sync'] ) && $feature->slug === $index_meta['feature_sync'] ) { + $feature_classes .= ' feature-syncing'; } ob_start(); ?> -
+

- title ); ?> + title ); ?>

- output_module_box(); ?> + output_feature_box(); ?>
- output_settings_box(); ?> + output_settings_box(); ?>
diff --git a/includes/intro-page.php b/includes/intro-page.php index 9b8142abff..8e489e8646 100644 --- a/includes/intro-page.php +++ b/includes/intro-page.php @@ -24,7 +24,7 @@

Qbox. If you have a bigger website, 10up provides Elasticsearch hosting via ElasticPress.io.", 'elasticpress' ); ?>

- "> + ">
diff --git a/lang/elasticpress.pot b/lang/elasticpress.pot index 47c4b87d5e..ccfddadfaf 100644 --- a/lang/elasticpress.pot +++ b/lang/elasticpress.pot @@ -2,9 +2,9 @@ # This file is distributed under the GPLv2 or later. msgid "" msgstr "" -"Project-Id-Version: ElasticPress 2.1.1\n" +"Project-Id-Version: ElasticPress 2.2\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/elasticpress\n" -"POT-Creation-Date: 2016-11-13 22:19:22+00:00\n" +"POT-Creation-Date: 2016-11-14 00:30:34+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -14,39 +14,41 @@ msgstr "" "X-Generator: grunt-wp-i18n 0.5.4\n" #: bin/wp-cli.php:46 bin/wp-cli.php:93 -msgid "No module with that slug is registered" +msgid "No feature with that slug is registered" msgstr "" #: bin/wp-cli.php:56 -msgid "This module is already active" +msgid "This feature is already active" msgstr "" #: bin/wp-cli.php:60 -msgid "Module depedencies are not met" +msgid "Feature depedencies are not met" msgstr "" #: bin/wp-cli.php:68 -msgid "This module requires a re-index. You may want to run the index command next." +msgid "" +"This feature requires a re-index. You may want to run the index command " +"next." msgstr "" #: bin/wp-cli.php:77 -msgid "Module activated" +msgid "Feature activated" msgstr "" #: bin/wp-cli.php:107 -msgid "Module is not active" +msgid "Feature is not active" msgstr "" #: bin/wp-cli.php:116 -msgid "Module deactivated" +msgid "Feature deactivated" msgstr "" #: bin/wp-cli.php:137 -msgid "Active modules:" +msgid "Active features:" msgstr "" #: bin/wp-cli.php:139 -msgid "Registered modules:" +msgid "Registered features:" msgstr "" #: bin/wp-cli.php:170 @@ -137,27 +139,27 @@ msgstr "" msgid "Unable to reach Elasticsearch Server! Check that service is running." msgstr "" -#: classes/class-ep-api.php:1948 +#: classes/class-ep-api.php:2027 msgid "" "Invalid response from ElasticPress server. Please contact your " "administrator." msgstr "" -#: classes/class-ep-api.php:1961 +#: classes/class-ep-api.php:2040 msgid "" "Site not indexed.

Please run: wp elasticpress index --setup " "--network-wide using WP-CLI. Or use the index button on the left of " "this screen.

" msgstr "" -#: classes/class-ep-api.php:1965 +#: classes/class-ep-api.php:2044 msgid "" "Site not indexed.

Please run: wp elasticpress index --setup " "using WP-CLI. Or use the index button on the left of this screen.

" msgstr "" -#: classes/class-ep-api.php:2003 classes/class-ep-api.php:2062 -#: classes/class-ep-api.php:2102 classes/class-ep-api.php:2153 +#: classes/class-ep-api.php:2082 classes/class-ep-api.php:2141 +#: classes/class-ep-api.php:2181 classes/class-ep-api.php:2232 msgid "Elasticsearch Host is not available." msgstr "" @@ -202,7 +204,7 @@ msgid "Security error!" msgstr "" #: classes/class-ep-dashboard.php:590 classes/class-ep-dashboard.php:591 -#: classes/class-ep-module.php:254 includes/settings-page.php:27 +#: classes/class-ep-feature.php:254 includes/settings-page.php:27 #: vendor/woocommerce/includes/admin/class-wc-admin-menus.php:80 #: vendor/woocommerce/includes/admin/settings/class-wc-settings-api.php:45 #: vendor/woocommerce/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php:52 @@ -215,17 +217,17 @@ msgstr "" msgid "Welcome" msgstr "" -#: classes/class-ep-module.php:218 +#: classes/class-ep-feature.php:218 #: vendor/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-product-data.php:616 #: vendor/woocommerce/includes/gateways/simplify-commerce/class-wc-gateway-simplify-commerce.php:98 msgid "Learn more" msgstr "" -#: classes/class-ep-module.php:223 +#: classes/class-ep-feature.php:223 msgid "Collapse" msgstr "" -#: classes/class-ep-module.php:257 +#: classes/class-ep-feature.php:257 #: vendor/woocommerce/includes/admin/class-wc-admin-post-types.php:272 #: vendor/woocommerce/includes/admin/class-wc-admin-webhooks-table-list.php:41 #: vendor/woocommerce/includes/admin/meta-boxes/class-wc-meta-box-product-data.php:671 @@ -236,7 +238,7 @@ msgstr "" msgid "Status" msgstr "" -#: classes/class-ep-module.php:259 +#: classes/class-ep-feature.php:259 #: vendor/woocommerce/includes/admin/meta-boxes/views/html-variation-admin.php:76 #: vendor/woocommerce/includes/admin/settings/class-wc-settings-checkout.php:292 #: vendor/woocommerce/includes/admin/settings/class-wc-settings-emails.php:269 @@ -247,13 +249,13 @@ msgstr "" msgid "Enabled" msgstr "" -#: classes/class-ep-module.php:260 +#: classes/class-ep-feature.php:260 #: vendor/woocommerce/includes/admin/settings/class-wc-settings-emails.php:271 #: vendor/woocommerce/includes/wc-webhook-functions.php:25 msgid "Disabled" msgstr "" -#: classes/class-ep-module.php:273 +#: classes/class-ep-feature.php:273 #: vendor/woocommerce/includes/admin/meta-boxes/views/html-order-items.php:246 #: vendor/woocommerce/templates/myaccount/form-reset-password.php:47 msgid "Save" @@ -277,53 +279,7 @@ msgstr "" msgid "The following values do not describe a valid date: month %1$s, day %2$s." msgstr "" -#: includes/dashboard-page.php:24 -msgid "List of modules" -msgstr "" - -#: includes/dashboard-page.php:53 -msgid "settings" -msgstr "" - -#: includes/intro-page.php:24 -msgid "A Fast and Flexible Search and Query Engine for WordPress." -msgstr "" - -#: includes/intro-page.php:25 -msgid "" -"You're almost there! The plugin is free to use but requires an " -"Elasticsearch server behind-the-scenes. There are tons of services that let " -"you easily get one like Qbox. If you have a " -"bigger website, 10up provides Elasticsearch hosting via ElasticPress.io." -msgstr "" - -#: includes/intro-page.php:31 -msgid "Set Up" -msgstr "" - -#: includes/intro-page.php:32 -#: vendor/woocommerce/includes/admin/class-wc-admin-setup-wizard.php:771 -msgid "Learn More" -msgstr "" - -#: includes/settings-page.php:36 -msgid "Elasticsearch Host" -msgstr "" - -#: includes/settings-page.php:40 -msgid "Your Elasticsearch host is set in wp-config.php" -msgstr "" - -#: includes/settings-page.php:42 -msgid "Plug in your Elasticsearch server here!" -msgstr "" - -#: includes/settings-page.php:50 -msgid "Save Changes" -msgstr "" - -#: modules/admin/admin.php:81 +#: features/admin/admin.php:81 msgid "" "Help editors more effectively browse through content. Load long lists of " "posts faster. Filter posts faster. Please note this syncs draft content to " @@ -331,75 +287,69 @@ msgid "" "properly secured." msgstr "" -#: modules/admin/admin.php:92 +#: features/admin/admin.php:92 msgid "" "Within the admin panel, posts and pages are shown in a standarized easy to " "use table format. After activating an SEO plugin, increasing post per " "pages, and making other modifications, that table view loads very slowly." msgstr "" -#: modules/admin/admin.php:94 +#: features/admin/admin.php:94 msgid "" "ElasticPress admin will make your admin curation experience much faster and " "easier. No longer will you have to wait 60 seconds to do things that should " "be easy such as viewing 200 posts at once." msgstr "" -#: modules/admin/admin.php:96 -msgid "" -"Using the search module in conjunction with this module will supercharge " -"your admin search." -msgstr "" - -#: modules/admin/admin.php:127 +#: features/admin/admin.php:126 msgid "" "You aren't using ElasticPress.io so " "we can't be sure your Elasticsearch instance is secure." msgstr "" -#: modules/related-posts/related-posts.php:76 +#: features/related-posts/related-posts.php:76 msgid "Help users easily find related content with a widget that just works." msgstr "" -#: modules/related-posts/related-posts.php:87 +#: features/related-posts/related-posts.php:87 msgid "" "Showing users related content is a quick way to improve readership and " "loyalty. There are a number of plugins that show related content, most of " "which are ineffective and slow." msgstr "" -#: modules/related-posts/related-posts.php:89 +#: features/related-posts/related-posts.php:89 msgid "" "ElasticPress has a powerful content matching algorithm that lets it find " -"related content very effectively. This module will create a widget for you " +"related content very effectively. This feature will create a widget for you " "to place into any sidebar or widgetized area." msgstr "" -#: modules/related-posts/widget.php:17 +#: features/related-posts/widget.php:17 msgid "" "Show related posts using ElasticPress. This widget will only appear on " "single post, page, and custom type pages." msgstr "" -#: modules/related-posts/widget.php:18 +#: features/related-posts/widget.php:18 msgid "ElasticPress - Related Posts" msgstr "" -#: modules/related-posts/widget.php:88 +#: features/related-posts/widget.php:88 msgid "Title:" msgstr "" -#: modules/related-posts/widget.php:96 +#: features/related-posts/widget.php:96 msgid "Number of Posts to Show:" msgstr "" -#: modules/search/search.php:16 +#: features/search/search.php:16 msgid "" "Beef up your search to be more accurate, search tags, categories, and other " "taxonomies, catch misspellings, weight content by recency and more." msgstr "" -#: modules/search/search.php:27 +#: features/search/search.php:27 msgid "" "Search is a long neglected piece of WordPress. Result relevancy is poor; " "performance is poor; there is no handling of misspellings; there is no way " @@ -407,31 +357,25 @@ msgid "" "only searches post content, excerpt, and title." msgstr "" -#: modules/search/search.php:30 +#: features/search/search.php:30 msgid "" -"The search module allows you to do all these things and more. Just " -"activating the module will make your search experience much better. Your " +"The search feature allows you to do all these things and more. Just " +"activating the feature will make your search experience much better. Your " "users will be able to more effectively browse your website and find the " "content they desire. Misspellings will be accounted for, categories " "searched, and results weighted by recency. If activated in conjunction with " -"the admin module, admin search will be improved as well." +"the admin feature, admin search will be improved as well." msgstr "" -#: modules/search/search.php:34 -msgid "" -"This module is a must have for all websites which is why " -"it's activated by default." -msgstr "" - -#: modules/woocommerce/woocommerce.php:533 +#: features/woocommerce/woocommerce.php:533 msgid "" "Allow customers to filter through products faster and improve product " "search relevancy. Enable editors to find orders and products more " -"effectively in the admin. This module will increase your sales bottom line " +"effectively in the admin. This feature will increase your sales bottom line " "and reduce administrative costs." msgstr "" -#: modules/woocommerce/woocommerce.php:544 +#: features/woocommerce/woocommerce.php:544 msgid "" "Running eCommerce stores is hard enough already. You should not have to " "worry about slow load times. ElasticPress WooCommerce supercharges all " @@ -440,17 +384,63 @@ msgid "" "fast." msgstr "" -#: modules/woocommerce/woocommerce.php:546 +#: features/woocommerce/woocommerce.php:546 msgid "" "In the admin, order management and fulfillment is supercharged. Finding " "orders is much easier with more relevant searches. View order lists is " "easier since they load faster." msgstr "" -#: modules/woocommerce/woocommerce.php:560 +#: features/woocommerce/woocommerce.php:560 msgid "WooCommerce not installed." msgstr "" +#: includes/dashboard-page.php:24 +msgid "List of features" +msgstr "" + +#: includes/dashboard-page.php:53 +msgid "settings" +msgstr "" + +#: includes/intro-page.php:24 +msgid "A Fast and Flexible Search and Query Engine for WordPress." +msgstr "" + +#: includes/intro-page.php:25 +msgid "" +"You're almost there! The plugin is free to use but requires an " +"Elasticsearch server behind-the-scenes. There are tons of services that let " +"you easily get one like Qbox. If you have a " +"bigger website, 10up provides Elasticsearch hosting via ElasticPress.io." +msgstr "" + +#: includes/intro-page.php:31 +msgid "Set Up" +msgstr "" + +#: includes/intro-page.php:32 +#: vendor/woocommerce/includes/admin/class-wc-admin-setup-wizard.php:771 +msgid "Learn More" +msgstr "" + +#: includes/settings-page.php:36 +msgid "Elasticsearch Host" +msgstr "" + +#: includes/settings-page.php:40 +msgid "Your Elasticsearch host is set in wp-config.php" +msgstr "" + +#: includes/settings-page.php:42 +msgid "Plug in your Elasticsearch server here!" +msgstr "" + +#: includes/settings-page.php:50 +msgid "Save Changes" +msgstr "" + #: vendor/woocommerce/i18n/continents.php:19 msgid "Africa" msgstr "" diff --git a/tests/modules/test-admin.php b/tests/features/test-admin.php similarity index 87% rename from tests/modules/test-admin.php rename to tests/features/test-admin.php index c4a08292a7..23b3793c63 100644 --- a/tests/modules/test-admin.php +++ b/tests/features/test-admin.php @@ -1,6 +1,6 @@ setup_test_post_type(); - delete_option( 'ep_active_modules' ); + delete_option( 'ep_active_features' ); } /** @@ -46,7 +46,7 @@ public function tearDown() { } /** - * Test main query isn't integrated when module isn't on + * Test main query isn't integrated when feature isn't on * * @since 2.1 * @group admin @@ -54,7 +54,7 @@ public function tearDown() { public function testAdminNotOn() { set_current_screen( 'edit.php' ); - EP_Modules::factory()->setup_modules(); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); @@ -74,7 +74,7 @@ public function testAdminNotOn() { } /** - * Test main query is integrated with module on + * Test main query is integrated with feature on * * @since 2.1 * @group admin @@ -82,8 +82,8 @@ public function testAdminNotOn() { public function testAdminOn() { set_current_screen( 'edit.php' ); - ep_activate_module( 'admin' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'admin' ); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); @@ -103,7 +103,7 @@ public function testAdminOn() { } /** - * Test main query on is integrated on drafts with module on + * Test main query on is integrated on drafts with feature on * * @since 2.1 * @group admin @@ -111,8 +111,8 @@ public function testAdminOn() { public function testAdminOnDraft() { set_current_screen( 'edit.php' ); - ep_activate_module( 'admin' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'admin' ); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); ep_create_and_sync_post( array( 'post_status' => 'draft' ) ); @@ -147,8 +147,8 @@ public function testAdminOnDraft() { public function testAdminOnDraftUpdated() { set_current_screen( 'edit.php' ); - ep_activate_module( 'admin' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'admin' ); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); $post_id = ep_create_and_sync_post(); @@ -185,8 +185,8 @@ public function testAdminOnDraftUpdated() { public function testAdminCategories() { set_current_screen( 'edit.php' ); - ep_activate_module( 'admin' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'admin' ); + EP_Features::factory()->setup_features(); $cat1 = wp_create_category( 'category one' ); $cat2 = wp_create_category( 'category two' ); diff --git a/tests/modules/test-related-posts.php b/tests/features/test-related-posts.php similarity index 92% rename from tests/modules/test-related-posts.php rename to tests/features/test-related-posts.php index 7393b4446b..779a27913a 100644 --- a/tests/modules/test-related-posts.php +++ b/tests/features/test-related-posts.php @@ -1,6 +1,6 @@ setup_modules(); + EP_Features::factory()->setup_features(); add_action( 'ep_related_html_attached', array( $this, 'action_ep_related_html_attached' ), 10 ); @@ -135,9 +135,9 @@ public function testFindRelatedPostFilter(){ ep_refresh_index(); - ep_activate_module( 'related_posts' ); + ep_activate_feature( 'related_posts' ); - EP_Modules::factory()->setup_modules(); + EP_Features::factory()->setup_features(); add_filter( 'ep_find_related_args', array( $this, 'find_related_posts_filter' ), 10, 1 ); $related = ep_find_related( $post_id ); diff --git a/tests/modules/test-search.php b/tests/features/test-search.php similarity index 91% rename from tests/modules/test-search.php rename to tests/features/test-search.php index affdf2fba6..16a043c814 100644 --- a/tests/modules/test-search.php +++ b/tests/features/test-search.php @@ -1,6 +1,6 @@ setup_modules(); + EP_Features::factory()->setup_features(); $post_ids = array(); ep_create_and_sync_post(); @@ -75,8 +75,8 @@ public function testSearchOff() { * @group search */ public function testSearchOn() { - ep_activate_module( 'search' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'search' ); + EP_Features::factory()->setup_features(); $post_ids = array(); @@ -103,8 +103,8 @@ public function testSearchOn() { public function testSearchIndexDeleted(){ global $wpdb; - ep_activate_module( 'search' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'search' ); + EP_Features::factory()->setup_features(); $post_ids = array(); diff --git a/tests/modules/test-woocommerce.php b/tests/features/test-woocommerce.php similarity index 86% rename from tests/modules/test-woocommerce.php rename to tests/features/test-woocommerce.php index 75c7720cdb..9beeacf9d5 100644 --- a/tests/modules/test-woocommerce.php +++ b/tests/features/test-woocommerce.php @@ -1,6 +1,6 @@ setup_test_post_type(); - delete_option( 'ep_active_modules' ); + delete_option( 'ep_active_features' ); } /** @@ -44,13 +44,13 @@ public function tearDown() { } /** - * Test products post type query doesn't get integrated when the module is not active + * Test products post type query doesn't get integrated when the feature is not active * * @since 2.1 * @group Wwoocommerce */ public function testProductsPostTypeQueryOff() { - EP_Modules::factory()->setup_modules(); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); ep_create_and_sync_post( array( 'post_content' => 'product 1', 'post_type' => 'product' ) ); @@ -69,14 +69,14 @@ public function testProductsPostTypeQueryOff() { } /** - * Test products post type query does get integrated when the module is not active + * Test products post type query does get integrated when the feature is not active * * @since 2.1 * @group woocommerce */ public function testProductsPostTypeQueryOn() { - ep_activate_module( 'woocommerce' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'woocommerce' ); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); ep_create_and_sync_post( array( 'post_content' => 'product 1', 'post_type' => 'product' ) ); @@ -103,8 +103,8 @@ public function testProductsPostTypeQueryOn() { * @group woocommerce */ public function testProductsPostTypeQueryShopOrder() { - ep_activate_module( 'woocommerce' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'woocommerce' ); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); ep_create_and_sync_post( array( 'post_type' => 'shop_order' ) ); @@ -131,8 +131,8 @@ public function testProductsPostTypeQueryShopOrder() { * @group woocommerce */ public function testProductsPostTypeQueryProductCatTax() { - ep_activate_module( 'woocommerce' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'woocommerce' ); + EP_Features::factory()->setup_features(); ep_create_and_sync_post(); @@ -162,8 +162,8 @@ public function testProductsPostTypeQueryProductCatTax() { * @group woocommerce */ public function testSearchOnShopOrderAdmin() { - ep_activate_module( 'woocommerce' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'woocommerce' ); + EP_Features::factory()->setup_features(); ep_create_and_sync_post( array( 'post_content' => 'findme', 'post_type' => 'shop_order' ) ); @@ -190,8 +190,8 @@ public function testSearchOnShopOrderAdmin() { * @group woocommerce */ public function testSearchOnAllFrontEnd() { - ep_activate_module( 'woocommerce' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'woocommerce' ); + EP_Features::factory()->setup_features(); add_action( 'ep_wp_query_search', array( $this, 'action_wp_query_search' ), 10, 0 ); diff --git a/tests/test-multisite.php b/tests/test-multisite.php index 1c42e9e9a9..a3a9136bd4 100644 --- a/tests/test-multisite.php +++ b/tests/test-multisite.php @@ -42,8 +42,8 @@ public function setUp() { /** * Most of our search test are bundled into core tests for legacy reasons */ - ep_activate_module( 'search' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'search' ); + EP_Features::factory()->setup_features(); } /** diff --git a/tests/test-single-site.php b/tests/test-single-site.php index 392f8fc381..b646147ada 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -33,8 +33,8 @@ public function setUp() { /** * Most of our search test are bundled into core tests for legacy reasons */ - ep_activate_module( 'search' ); - EP_Modules::factory()->setup_modules(); + ep_activate_feature( 'search' ); + EP_Features::factory()->setup_features(); } /** @@ -3241,40 +3241,40 @@ public function testPostParentQuery() { } /** - * Test register module + * Test register feature * * @since 2.1 */ - public function testRegisterModule() { - ep_register_module( 'test', array( + public function testRegisterFeature() { + ep_register_feature( 'test', array( 'title' => 'Test', ) ); - $this->assertTrue( ! empty( EP_Modules::factory()->registered_modules['test'] ) ); - $this->assertTrue( ! empty( ep_get_registered_module( 'test' ) ) ); + $this->assertTrue( ! empty( EP_Features::factory()->registered_features['test'] ) ); + $this->assertTrue( ! empty( ep_get_registered_feature( 'test' ) ) ); } /** - * Test setup modules + * Test setup features * * @since 2.1 */ - public function testSetupModules() { - ep_register_module( 'test', array( + public function testSetupFeatures() { + ep_register_feature( 'test', array( 'title' => 'Test', ) ); - ep_activate_module( 'test' ); + ep_activate_feature( 'test' ); - $module = ep_get_registered_module( 'test' ); + $feature = ep_get_registered_feature( 'test' ); - $this->assertTrue( ! empty( $module ) ); + $this->assertTrue( ! empty( $feature ) ); - $this->assertTrue( ! $module->is_active() ); + $this->assertTrue( ! $feature->is_active() ); - EP_Modules::factory()->setup_modules(); + EP_Features::factory()->setup_features(); - $this->assertTrue( $module->is_active() ); + $this->assertTrue( $feature->is_active() ); } /** diff --git a/uninstall.php b/uninstall.php index 440df907e5..24321608dc 100644 --- a/uninstall.php +++ b/uninstall.php @@ -62,8 +62,8 @@ protected static function clean_options() { delete_option( 'ep_host' ); delete_site_option( 'ep_index_meta' ); delete_option( 'ep_index_meta' ); - delete_site_option( 'ep_active_modules' ); - delete_option( 'ep_active_modules' ); + delete_site_option( 'ep_feature_settings' ); + delete_option( 'ep_feature_settings' ); } /** From 405ec855edc2af57c5bdff04131b9d375d8a8289 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 14 Nov 2016 09:03:25 -0500 Subject: [PATCH 032/159] Clean up tax filters; no need for separate sanitize method --- classes/class-ep-api.php | 62 +++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 66620eac60..95918f7283 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -997,18 +997,26 @@ public function format_args( $args ) { //set tax_query if it's implicitly set in the query //e.g. $args['tag'], $args['category_name'] if ( empty( $args['tax_query'] ) ) { + if ( ! empty( $args['category_name'] ) ) { + $args['tax_query'][] = array( + 'taxonomy' => 'category', + 'terms' => array( $args['category_name'] ), + 'field' => 'slug' + ); + } elseif ( ! empty( $args['cat'] ) ) { + $args['tax_query'][] = array( + 'taxonomy' => 'category', + 'terms' => array( $args['cat'] ), + 'field' => 'id' + ); + } - $taxonomies = get_taxonomies(); - $taxonomies = $this->sanitize_taxonomy_names($taxonomies); //fix it up - - foreach( $taxonomies as $tax => $taxName ){ - if( isset( $args[ $taxName ] ) && ! empty( $args[ $taxName ] ) ){ - $args['tax_query'][] = array( - 'taxonomy' => $tax, - 'terms' => array($args[ $taxName ]), - 'field' => 'slug' - ); - } + if ( ! empty( $args['tag'] ) ) { + $args['tax_query'][] = array( + 'taxonomy' => 'post_tag', + 'terms' => array( $args['tag'] ), + 'field' => 'slug' + ); } } @@ -1054,6 +1062,9 @@ public function format_args( $args ) { } } + /** + * Todo: This needs to be fixed + */ if ( ! empty( $tax_filter ) ) { $relation = 'must'; @@ -1062,9 +1073,13 @@ public function format_args( $args ) { } } - if( ! empty( $tax_must_not_filter ) ) { + if ( ! empty( $tax_must_not_filter ) ) { $es_tax_query['must_not'] = $tax_must_not_filter; } + + if ( ! empty( $tax_filter ) ) { + $es_tax_query['must'] = $tax_filter; + } if( ! empty( $es_tax_query ) ) { $filter['bool']['must'][]['bool'] = $es_tax_query; @@ -1670,29 +1685,6 @@ public function format_args( $args ) { return apply_filters( 'ep_formatted_args', $formatted_args, $args ); } - /** - * WP is using 'weird' taxonomy name, for example: 'category' but in query using 'category_name', 'post_tag' but in queries using 'tag' - * Map taxonomy name in db to taxonomy in query - * @param $taxonomies - * @return array - */ - protected function sanitize_taxonomy_names( $taxonomies ){ - $taxes = array(); - foreach( $taxonomies as $tax => $taxName ){ - switch( $tax ){ - case "category": - $taxes["category"] = "category_name"; - break; - case "post_tag": - $taxes["post_tag"] = "tag"; - break; - default: - $taxes[ $tax ] = $taxName; - } - } - return $taxes; - } - /** * Wrapper function for get_sites - allows us to have one central place for the `ep_indexable_sites` filter * From 9dc34245e20a0070f5be62010ae8015fff3deb97 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 14 Nov 2016 09:09:44 -0500 Subject: [PATCH 033/159] Fix menu priority --- classes/class-ep-dashboard.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 149f744a9a..de4ccb5f5e 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -581,8 +581,7 @@ public function action_admin_menu() { $capability, 'elasticpress', array( $this, 'dashboard_page' ), - 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNzMgNzEuMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNzMgNzEuMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik0zNi41LDQuN0MxOS40LDQuNyw1LjYsMTguNiw1LjYsMzUuN2MwLDEwLDQuNywxOC45LDEyLjEsMjQuNWw0LjUtNC41YzAuMS0wLjEsMC4xLTAuMiwwLjItMC4zbDAuNy0wLjdsNi40LTYuNGMyLjEsMS4yLDQuNSwxLjksNy4xLDEuOWM4LDAsMTQuNS02LjUsMTQuNS0xNC41cy02LjUtMTQuNS0xNC41LTE0LjVTMjIsMjcuNiwyMiwzNS42YzAsMi44LDAuOCw1LjMsMi4xLDcuNWwtNi40LDYuNGMtMi45LTMuOS00LjYtOC43LTQuNi0xMy45YzAtMTIuOSwxMC41LTIzLjQsMjMuNC0yMy40czIzLjQsMTAuNSwyMy40LDIzLjRTNDkuNCw1OSwzNi41LDU5Yy0yLjEsMC00LjEtMC4zLTYtMC44bC0wLjYsMC42bC01LjIsNS40YzMuNiwxLjUsNy42LDIuMywxMS44LDIuM2MxNy4xLDAsMzAuOS0xMy45LDMwLjktMzAuOVM1My42LDQuNywzNi41LDQuN3oiLz48L3N2Zz4=', - 3 + 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNzMgNzEuMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNzMgNzEuMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik0zNi41LDQuN0MxOS40LDQuNyw1LjYsMTguNiw1LjYsMzUuN2MwLDEwLDQuNywxOC45LDEyLjEsMjQuNWw0LjUtNC41YzAuMS0wLjEsMC4xLTAuMiwwLjItMC4zbDAuNy0wLjdsNi40LTYuNGMyLjEsMS4yLDQuNSwxLjksNy4xLDEuOWM4LDAsMTQuNS02LjUsMTQuNS0xNC41cy02LjUtMTQuNS0xNC41LTE0LjVTMjIsMjcuNiwyMiwzNS42YzAsMi44LDAuOCw1LjMsMi4xLDcuNWwtNi40LDYuNGMtMi45LTMuOS00LjYtOC43LTQuNi0xMy45YzAtMTIuOSwxMC41LTIzLjQsMjMuNC0yMy40czIzLjQsMTAuNSwyMy40LDIzLjRTNDkuNCw1OSwzNi41LDU5Yy0yLjEsMC00LjEtMC4zLTYtMC44bC0wLjYsMC42bC01LjIsNS40YzMuNiwxLjUsNy42LDIuMywxMS44LDIuM2MxNy4xLDAsMzAuOS0xMy45LDMwLjktMzAuOVM1My42LDQuNywzNi41LDQuN3oiLz48L3N2Zz4=' ); add_submenu_page( From 32ef51b5708837faba5cd7a165e43e47a1ba501e Mon Sep 17 00:00:00 2001 From: David Arceneaux Date: Mon, 14 Nov 2016 16:00:15 -0800 Subject: [PATCH 034/159] replace deprecated function and trim whitespace, because that's what my editor is config'ed to do --- classes/class-ep-sync-manager.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index 3148622f54..cc4aea8568 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -21,7 +21,7 @@ public function __construct() { } /** * Save posts for indexing later - * + * * @since 2.0 * @var array */ @@ -45,7 +45,7 @@ public function setup() { add_action( 'added_post_meta', array( $this, 'action_queue_meta_sync' ), 10, 4 ); add_action( 'shutdown', array( $this, 'action_index_sync_queue' ) ); } - + /** * Remove actions and filters * @@ -84,7 +84,7 @@ public function action_index_sync_queue() { /** * When whitelisted meta is updated, queue the post for reindex - * + * * @param int $meta_id * @param int $object_id * @param string $meta_key @@ -94,7 +94,7 @@ public function action_index_sync_queue() { public function action_queue_meta_sync( $meta_id, $object_id, $meta_key, $meta_value ) { global $importer; - if ( ! ep_elasticsearch_alive() ) { + if ( ! ep_elasticsearch_can_connect() ) { return; } @@ -102,7 +102,7 @@ public function action_queue_meta_sync( $meta_id, $object_id, $meta_key, $meta_v if ( ! empty( $importer ) ) { return; } - + $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $object_id ); @@ -177,7 +177,7 @@ public function action_sync_on_update( $post_ID ) { if ( ! empty( $importer ) ) { return; } - + $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $post_ID ); @@ -216,7 +216,7 @@ public function action_sync_on_update( $post_ID ) { } } } - + /** * Return a singleton instance of the current class * From 08836d0a7c8be833253a97de5837bc8f19116659 Mon Sep 17 00:00:00 2001 From: Lukas Pawlik Date: Thu, 17 Nov 2016 12:37:16 +0000 Subject: [PATCH 035/159] Use correct value for aggregation --- classes/class-ep-api.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index d9212ee3af..7910afdc3d 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -196,11 +196,11 @@ public function query( $args, $query_args, $scope = 'current' ) { ); $request = ep_remote_request( $path, apply_filters( 'ep_search_request_args', $request_args, $args, $scope, $query_args ), $query_args ); - + $remote_req_res_code = intval( wp_remote_retrieve_response_code( $request ) ); - + $is_valid_res = ( $remote_req_res_code >= 200 && $remote_req_res_code <= 299 ); - + if ( ! is_wp_error( $request ) && apply_filters( 'ep_remote_request_is_valid_res', $is_valid_res, $request ) ) { // Allow for direct response retrieval @@ -437,7 +437,7 @@ public function create_network_alias( $indexes ) { * @return array|bool|mixed */ public function put_mapping() { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $es_version = get_site_option( 'ep_es_version', false ); } else { $es_version = get_option( 'ep_es_version', false ); @@ -1629,7 +1629,7 @@ public function format_args( $args ) { $formatted_args['aggs'][ $agg_name ]['filter'] = $filter; $formatted_args['aggs'][ $agg_name ]['aggs'] = $agg_obj['aggs']; } else { - $formatted_args['aggs'][ $agg_name ] = $args['aggs']; + $formatted_args['aggs'][ $agg_name ] = $agg_obj['aggs']; } } return apply_filters( 'ep_formatted_args', $formatted_args, $args ); @@ -1669,7 +1669,7 @@ public function get_sites( $limit = 0 ) { $args = apply_filters( 'ep_indexable_sites_args', array( 'limit' => $limit, ) ); - + if ( function_exists( 'get_sites' ) ) { $site_objects = get_sites( $args ); $sites = array(); From 89d51bb6629ee6ce11db73a12d59efed54839307 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Mon, 21 Nov 2016 23:39:51 +0530 Subject: [PATCH 036/159] Instead of resetting, set transient for each index request while indexing via cli --- bin/wp-cli.php | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index 315c1a52fc..bb279084a4 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -30,13 +30,6 @@ class ElasticPress_CLI_Command extends WP_CLI_Command { */ private $failed_posts_message = array(); - /** - * Holds timestamp of last time transient was set - * - * @since 2.1.1 - */ - private $last_transient_set_time = false; - /** * Holds whether it's network transient or not * @@ -366,8 +359,6 @@ public function index( $args, $assoc_args ) { } timer_start(); - - $this->last_transient_set_time = time(); // Run setup if flag was passed if ( isset( $assoc_args['setup'] ) && true === $assoc_args['setup'] ) { @@ -505,7 +496,7 @@ private function _index_helper( $args ) { // index the posts one-by-one. not sure why someone may want to do this. $result = ep_sync_post( get_the_ID() ); - $this->maybe_reset_transient(); + $this->reset_transient(); do_action( 'ep_cli_post_index', get_the_ID() ); } else { @@ -649,7 +640,7 @@ private function bulk_index( $show_bulk_errors = false ) { // decode the response $response = ep_bulk_index_posts( $body ); - $this->maybe_reset_transient(); + $this->reset_transient(); do_action( 'ep_cli_post_bulk_index', $this->posts ); @@ -834,19 +825,15 @@ private function _connect_check() { } /** - * Maybe reset transient while indexing + * Reset transient while indexing * * @since 2.1.1 */ - private function maybe_reset_transient() { - if( ( time() - $this->last_transient_set_time ) >= ( $this->transient_expiration ) ) { - $this->last_transient_set_time = time(); - - if( $this->is_network_transient ) { - set_site_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); - } else { - set_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); - } + private function reset_transient() { + if( $this->is_network_transient ) { + set_site_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); + } else { + set_transient( 'ep_index_meta', array( 'wpcli' => true ), $this->transient_expiration ); } } } From 6beccf9f85f4bc692d6a8d78b93c910abb9c025b Mon Sep 17 00:00:00 2001 From: Kristoffer Svanmark Date: Tue, 22 Nov 2016 08:11:18 +0100 Subject: [PATCH 037/159] Allow for indexing of "inherit" post status if post type is "attachment" --- classes/class-ep-sync-manager.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index b647cbb2f0..5be9cb6ea3 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -102,6 +102,11 @@ public function action_queue_meta_sync( $meta_id, $object_id, $meta_key, $meta_v $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $object_id ); + // Allow inherit as post status if post type is attachment + if ( $post_type === 'attachment' ) { + $indexable_post_statuses[] = 'inherit'; + } + if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || 'revision' === $post_type ) { // Bypass saving if doing autosave or post type is revision return; From 21696d5ce768e88118d9e66a28c4e400bd63eaf8 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Tue, 22 Nov 2016 23:24:30 +0530 Subject: [PATCH 038/159] Fix image search in the media area and when searching an image in the feature product not working --- classes/class-ep-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 95918f7283..dba213af6e 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1622,7 +1622,7 @@ public function format_args( $args ) { if ( ! empty( $args['post_status'] ) ) { // should NEVER be "any" but just in case if ( 'any' !== $args['post_status'] ) { - $post_status = (array) $args['post_status']; + $post_status = (array) ( is_string( $args['post_status'] ) ? explode( ',', $args['post_status'] ) : $args['post_status'] ); $terms_map_name = 'terms'; if ( count( $post_status ) < 2 ) { $terms_map_name = 'term'; From fd29d7e20f84e1b6405985730d5ac1c7b1cda71c Mon Sep 17 00:00:00 2001 From: Ben Cumber Date: Wed, 30 Nov 2016 15:58:43 -0700 Subject: [PATCH 039/159] Potential mapping solution for #643 --- includes/mappings/5-0.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/includes/mappings/5-0.php b/includes/mappings/5-0.php index 864caf8baf..f92b8b923e 100644 --- a/includes/mappings/5-0.php +++ b/includes/mappings/5-0.php @@ -85,8 +85,7 @@ 'type' => 'text', 'fields' => array( 'sortable' => array( - 'type' => 'text', - 'analyzer' => 'ewp_lowercase', + 'type' => 'keyword', ), 'raw' => array( 'type' => 'keyword', @@ -135,8 +134,7 @@ 'type' => 'keyword', ), 'sortable' => array( - 'type' => 'text', - 'analyzer' => 'ewp_lowercase', + 'type' => 'keyword', ), ), ), @@ -183,8 +181,7 @@ 'type' => 'keyword', ), 'sortable' => array( - 'type' => 'text', - 'analyzer' => 'ewp_lowercase', + 'type' => 'keyword', ), ), ), @@ -195,8 +192,7 @@ 'type' => 'keyword', ), 'sortable' => array( - 'type' => 'text', - 'analyzer' => 'ewp_lowercase', + 'type' => 'keyword', ), ), ), @@ -227,8 +223,7 @@ 'type' => 'keyword', ), 'sortable' => array( - 'type' => 'text', - 'analyzer' => 'ewp_lowercase', + 'type' => 'keyword', ), ), ), From 1398d4e7bc59c57f9a625a305e2c8fe58a1edb0b Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 1 Dec 2016 01:03:23 -0500 Subject: [PATCH 040/159] Do proper connection check on elasticsearch --- classes/class-ep-api.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 95918f7283..132172e12c 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1915,7 +1915,12 @@ public function elasticsearch_can_connect() { if ( ! is_wp_error( $request ) ) { if ( isset( $request['response']['code'] ) && 200 === $request['response']['code'] ) { - return true; + + $response_body = json_decode( wp_remote_retrieve_body( $request ), true ); + + if ( ! empty( $response_body ) && ! empty( $response_body['name']) ) { + return true; + } } } From fa53b53aa93edae2b9a2d5a64a6177c9d3abc020 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 1 Dec 2016 01:24:36 -0500 Subject: [PATCH 041/159] Rethink elasticpress admin notices; fix bug where index meta wasnt getting checked properly for auto starting; updates features screenshot --- assets/js/admin.js | 16 +++ assets/js/admin.min.js | 2 +- classes/class-ep-dashboard.php | 194 ++++++++++++++++++++++----------- elasticpress.php | 34 ++++++ images/features-screenshot.png | Bin 0 -> 140086 bytes images/modules-screenshot.png | Bin 108456 -> 0 bytes uninstall.php | 8 ++ 7 files changed, 192 insertions(+), 62 deletions(-) create mode 100644 images/features-screenshot.png delete mode 100644 images/modules-screenshot.png diff --git a/assets/js/admin.js b/assets/js/admin.js index 9df19abd05..261e12b696 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -112,6 +112,9 @@ // We are mid sync if ( ep.auto_start_index ) { syncStatus = 'sync'; + + history.pushState( {}, document.title, document.location.pathname + document.location.search.replace( /&do_sync/, '' ) ); + updateSyncDash(); sync(); } else { @@ -127,6 +130,9 @@ // We are mid sync if ( ep.auto_start_index ) { syncStatus = 'sync'; + + history.pushState( {}, document.title, document.location.pathname + document.location.search.replace( /&do_sync/, '' ) ); + updateSyncDash(); sync(); } else { @@ -136,6 +142,16 @@ } } } + } else { + // Start a new sync automatically + if ( ep.auto_start_index ) { + syncStatus = 'sync'; + + history.pushState( {}, document.title, document.location.pathname + document.location.search.replace( /&do_sync/, '' ) ); + + updateSyncDash(); + sync(); + } } function updateSyncDash() { diff --git a/assets/js/admin.min.js b/assets/js/admin.min.js index 1915eb8e84..bce6a9592d 100644 --- a/assets/js/admin.min.js +++ b/assets/js/admin.min.js @@ -1 +1 @@ -!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=ep.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=ep.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=ep.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(ep.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null}else if("finished"===o){if(j.text(ep.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:ep.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",feature_sync:p,nonce:ep.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-features")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-feature"),e=g.find(".ep-feature-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_feature",feature:c,nonce:ep.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("feature-active"):e.removeClass("feature-active"),a.data.reindex&&(o="sync",e.addClass("feature-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("feature-active"),e.removeClass("feature-syncing")},700)})}),ep.index_meta&&(ep.index_meta.wpcli?(o="wpcli",b()):(q=ep.index_meta.offset,r=ep.index_meta.found_posts,ep.index_meta.feature_sync&&(p=ep.index_meta.feature_sync),ep.index_meta.current_site&&(e=ep.index_meta.current_site),ep.index_meta.site_stack&&(f=ep.index_meta.site_stack),f&&f.length?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):0!==r||ep.index_meta.start?ep.auto_start_index?(o="sync",b(),d()):(o="pause",b()):(o="finished",b()))),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file +!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=ep.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=ep.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=ep.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(ep.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null}else if("finished"===o){if(j.text(ep.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:ep.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",feature_sync:p,nonce:ep.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-features")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-feature"),e=g.find(".ep-feature-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_feature",feature:c,nonce:ep.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("feature-active"):e.removeClass("feature-active"),a.data.reindex&&(o="sync",e.addClass("feature-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("feature-active"),e.removeClass("feature-syncing")},700)})}),ep.index_meta?ep.index_meta.wpcli?(o="wpcli",b()):(q=ep.index_meta.offset,r=ep.index_meta.found_posts,ep.index_meta.feature_sync&&(p=ep.index_meta.feature_sync),ep.index_meta.current_site&&(e=ep.index_meta.current_site),ep.index_meta.site_stack&&(f=ep.index_meta.site_stack),f&&f.length?ep.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):0!==r||ep.index_meta.start?ep.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):(o="finished",b())):ep.auto_start_index&&(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 38760342c7..9a3e9be552 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -42,10 +42,8 @@ public function setup() { add_action( 'admin_init', array( $this, 'intro_or_dashboard' ) ); add_action( 'wp_ajax_ep_index', array( $this, 'action_wp_ajax_ep_index' ) ); add_action( 'wp_ajax_ep_cancel_index', array( $this, 'action_wp_ajax_ep_cancel_index' ) ); - add_action( 'admin_notices', array( $this, 'action_mid_index_notice' ) ); - add_action( 'network_admin_notices', array( $this, 'action_mid_index_notice' ) ); - add_action( 'admin_notices', array( $this, 'action_bad_host_notice' ) ); - add_action( 'network_admin_notices', array( $this, 'action_bad_host_notice' ) ); + add_action( 'admin_notices', array( $this, 'maybe_notice' ) ); + add_action( 'network_admin_notices', array( $this, 'maybe_notice' ) ); add_filter( 'plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 2 ); add_filter( 'network_admin_plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 2 ); } @@ -84,89 +82,157 @@ public function filter_plugin_action_links( $plugin_actions, $plugin_file ) { } /** - * Print out mid sync warning notice + * Output variety of dashboard notices. Only one at a time :) * - * @since 2.1 + * @since 2.2 */ - public function action_mid_index_notice() { - if ( isset( get_current_screen()->id ) && strpos( get_current_screen()->id, 'elasticpress' ) !== false ) { + public function maybe_notice() { + // Admins only + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + if ( ! is_super_admin() ) { + return; + } + } else { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + } + // Don't show notice on intro page ever + if ( ! empty( $_GET['page'] ) && 'elasticpress-intro' === $_GET['page'] ) { return; } + // If in network mode, don't output notice in admin and vice-versa if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { if ( ! is_network_admin() ) { return; } - - $url = admin_url( 'network/admin.php?page=elasticpress&resume_sync' ); - $index_meta = get_site_option( 'ep_index_meta', false ); } else { if ( is_network_admin() ) { return; } - - $url = admin_url( 'admin.php?page=elasticpress&resume_sync' ); - $index_meta = get_option( 'ep_index_meta', false ); } - if ( empty( $index_meta ) || ep_is_indexing_wpcli() ) { - return; - } + $notice = false; - ?> -
-

go back and finish it?', 'elasticpress' ), esc_url( $url ) ); ?>

-
- id ) || strpos( get_current_screen()->id, 'elasticpress' ) === false ) { - return; + $host = ep_get_host(); + + /** + * Bad host notice checks + */ + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $options_host = get_site_option( 'ep_host' ); + } else { + $options_host = get_option( 'ep_host' ); } - // Don't show message on intro page - if ( ! empty( $_GET['page'] ) && 'elasticpress-intro' === $_GET['page'] ) { - return; + if ( empty( $host ) || ! ep_elasticsearch_can_connect() ) { + if ( $on_settings_page ) { + if ( false !== $options_host || ( defined( 'EP_HOST' ) && EP_HOST ) ) { + $notice = 'bad-host'; + } + } else { + $notice = 'bad-host'; + } } + /** + * Never synced notice check + */ if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - if ( ! is_network_admin() ) { - return; - } + $last_sync = get_site_option( 'ep_last_sync', false ); + } else { + $last_sync = get_option( 'ep_last_sync', false ); + } - $url = admin_url( 'network/admin.php?page=elasticpress-settings' ); - $options_host = get_site_option( 'ep_host' ); + if ( false === $last_sync && ! isset( $_GET['do_sync'] ) ) { + $notice = 'no-sync'; + } + + /** + * Upgrade sync notice check + */ + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $need_upgrade_sync = get_site_option( 'ep_need_upgrade_sync', false ); } else { - if ( is_network_admin() ) { - return; - } + $need_upgrade_sync = get_option( 'ep_need_upgrade_sync', false ); + } - $url = admin_url( 'admin.php?page=elasticpress-settings' ); - $options_host = get_option( 'ep_host' ); + if ( $need_upgrade_sync && ! isset( $_GET['do_sync'] ) ) { + $notice = 'upgrade-sync'; } /** - * If we are on the setting screen and a host has never been set in the options table or defined, then let's - * assume this is our first time and not show an obvious message. + * Need setup up notice check */ - if ( ! empty( $_GET['page'] ) && 'elasticpress-settings' === $_GET['page'] && false === $options_host && ( ! defined( 'EP_HOST' ) || ! EP_HOST ) ) { - return; + + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $intro_shown = get_site_option( 'ep_intro_shown', false ); + } else { + $intro_shown = get_option( 'ep_intro_shown', false ); } - $host = ep_get_host(); + if ( ! $intro_shown && false === $options_host && ( ! defined( 'EP_HOST' ) || ! EP_HOST ) ) { + $notice = 'need-setup'; + } - if ( empty( $host ) || ! ep_elasticsearch_can_connect() ) { - ?> -
-

fix it for ElasticPress to work.', 'elasticpress' ), esc_url( $url ) ); ?>

-
- +
+

fix it for ElasticPress to work.', 'elasticpress' ), esc_url( $url ) ); ?>

+
+ +
+

quick set up process to get the plugin working.', 'elasticpress' ), esc_url( $url ) ); ?>

+
+ +
+

sync to get the plugin working.', 'elasticpress' ), esc_url( $url ) ); ?>

+
+ +
+

run a sync.', 'elasticpress' ), esc_url( $url ) ); ?>

+
+ wp_create_nonce( 'ep_nonce' ) ); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $index_meta = get_site_transient( 'ep_index_meta' ); + $index_meta = get_site_option( 'ep_index_meta' ); } else { - $index_meta = get_transient( 'ep_index_meta' ); + $index_meta = get_option( 'ep_index_meta' ); + } + + if ( isset( $_GET['do_sync'] ) ) { + $data['auto_start_index'] = true; } if ( ! empty( $index_meta ) ) { $data['index_meta'] = $index_meta; - - if ( isset( $_GET['resume_sync'] ) ) { - $data['auto_start_index'] = true; - } } $data['sync_complete'] = esc_html__( 'Sync complete', 'elasticpress' ); diff --git a/elasticpress.php b/elasticpress.php index a83708f263..9496da2d22 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -62,10 +62,15 @@ function ep_on_activate() { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $feature_settings = get_site_option( 'ep_feature_settings', false ); + $old_version = get_site_option( 'ep_version', false ); } else { $feature_settings = get_option( 'ep_feature_settings', false ); + $old_version = get_option( 'ep_version', false ); } + /** + * Run feature post activation hooks + */ if ( false === $feature_settings ) { $registered_features = EP_Features::factory()->registered_features; @@ -79,12 +84,41 @@ function ep_on_activate() { } } + /** + * Reindex if we cross a reindex version in the upgrade + */ + $reindex_versions = apply_filters( 'ep_reindex_versions', array( + '2.2', + ) ); + + $need_upgrade_sync = false; + + if ( false === $old_version ) { + $need_upgrade_sync = true; + } else { + $last_reindex_version = $reindex_versions[ count( $reindex_versions ) - 1 ]; + + if ( ( -1 === version_compare( $old_version, $last_reindex_version ) && 1 === version_compare( EP_VERSION , $last_reindex_version ) ) || 0 === version_compare( EP_VERSION , $last_reindex_version ) ) { + $last_reindex_version = true; + } + } + + if ( $need_upgrade_sync ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_need_upgrade_sync', true ); + } else { + update_option( 'ep_need_upgrade_sync', true ); + } + } + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { update_site_option( 'ep_feature_settings', $feature_settings ); delete_site_option( 'ep_index_meta' ); + update_site_option( 'ep_version', sanitize_text_field( EP_VERSION ) ); } else { update_option( 'ep_feature_settings', $feature_settings ); delete_option( 'ep_index_meta' ); + update_option( 'ep_version', sanitize_text_field( EP_VERSION ) ); } } register_activation_hook( __FILE__, 'ep_on_activate' ); diff --git a/images/features-screenshot.png b/images/features-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..5d848ef5686d4f2c7adb29b8aad09af4597d106a GIT binary patch literal 140086 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 D0y!;S literal 0 HcmV?d00001 diff --git a/images/modules-screenshot.png b/images/modules-screenshot.png deleted file mode 100644 index 7a69894568ad984f1bfbcd619b4fa0228556d72b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108456 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�UV6Sq4k`q}YAV|i$Dh)T(AcA}lW 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�~YWwM@2-!QQvaz!3lqD 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%0D2Flp#}Opzkbc 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�m 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ⅇ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 TxPE;M diff --git a/uninstall.php b/uninstall.php index 24321608dc..7729c7d67d 100644 --- a/uninstall.php +++ b/uninstall.php @@ -64,6 +64,14 @@ protected static function clean_options() { delete_option( 'ep_index_meta' ); delete_site_option( 'ep_feature_settings' ); delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_version' ); + delete_option( 'ep_version' ); + delete_option( 'ep_intro_shown' ); + delete_site_option( 'ep_intro_shown' ); + delete_option( 'ep_last_sync' ); + delete_site_option( 'ep_last_sync' ); + delete_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_need_upgrade_sync' ); } /** From 7d0665ef7035342df335a2974f7b7195e801639c Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 4 Dec 2016 13:41:29 -0500 Subject: [PATCH 042/159] Remove feature dependency relics; fix comments --- bin/wp-cli.php | 8 ++++++-- classes/class-ep-feature.php | 6 +++--- features/woocommerce/woocommerce.php | 1 - 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index bb279084a4..4d2d9f9471 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -70,8 +70,12 @@ public function activate_feature( $args, $assoc_args ) { WP_CLI::error( __( 'This feature is already active', 'elasticpress' ) ); } - if ( is_wp_error( $feature->dependencies_met() ) ) { - WP_CLI::error( __( 'Feature depedencies are not met', 'elasticpress' ) ); + $status = $feature->requirements_status(); + + if ( 2 === $status->code ) { + WP_CLI::error( __( 'Feature requirements are not met', 'elasticpress' ) ); + } elseif ( 1 === $status->code ) { + WP_CLI::warning( printf( __( 'Feature is usable but there are warnings: %s', 'elasticpress' ), $status->message ) ); } $active_features[] = $feature->slug; diff --git a/classes/class-ep-feature.php b/classes/class-ep-feature.php index ab47555e41..aa949a7cc3 100644 --- a/classes/class-ep-feature.php +++ b/classes/class-ep-feature.php @@ -32,9 +32,9 @@ public function __construct( $code, $message = null ) { /** * Returns the status of a feature * - * 1 is no issues (hollow green) - * 2 is usable but there are warnngs (hollow yellow) - * 3 is not usable (hollow red) + * 0 is no issues + * 1 is usable but there are warnngs + * 2 is not usable * * @var int * @since 2.2 diff --git a/features/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php index 2ee3e89abd..9b7a7b0542 100644 --- a/features/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -573,6 +573,5 @@ function ep_wc_requirements_status( $status ) { 'feature_box_summary_cb' => 'ep_wc_feature_box_summary', 'feature_box_long_cb' => 'ep_wc_feature_box_long', 'requires_install_reindex' => true, - 'dependencies_met_cb' => 'wc_dependencies_met_cb', ) ); From f2e051c1986693514458c4f55ecddc343ed94889 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 4 Dec 2016 13:45:57 -0500 Subject: [PATCH 043/159] Update requirements status documentation. Fixes #645 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 59be2cf81e..b39b2022b0 100644 --- a/README.md +++ b/README.md @@ -548,7 +548,7 @@ add_action( 'plugins_loaded', function() { 'feature_box_summary_cb' => 'summary_callback_function', 'feature_box_long_cb' => 'long_summary_callback_function', 'requires_install_reindex' => true, - 'dependencies_met_cb' => 'dependencies_meta_callback_function', + 'requirements_status_cb' => 'requirements_status_callback_function', 'post_activation_cb' => 'post_activation_callback_function', ) ); } ); @@ -562,7 +562,7 @@ The only arguments that are really required are the `slug` and `title` of the as * `post_activation_cb` (callback) - Callback to a function to be called after a feature is first activated. * `feature_box_summary_cb` (callback) - Callback to a function that outputs HTML feature box summary (short description of feature). * `feature_box_long_cb` (callback) - Callback to a function that outputs HTML feature box full description. -* `dependencies_met_cb` (callback) - Callback to a function that determines if the features dependencies are met. True means yes, WP_Error means no. If no, WP_Error message will be printed to the screen. +* `requirements_status_cb` (callback) - Callback to a function that determines if the features requirements are met. This function needs to return a `EP_Feature_Requirements_Status` object. `EP_Feature_Requirements_Status` is a simple class with a `code` and a `message` property. Code 0 means there are no requirement issues; code 1 means there are issues with warnings; code 2 means the feature does not have it's requirements met and cannot be used. The message property of the object can be used to store warnings. If you build an open source custom feature, let us know! We'd be happy to list the feature within ElasticPress documentation. From ad17dd25b4cd10940d9ab6ca1f0e8d3768517448 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 4 Dec 2016 19:26:38 -0500 Subject: [PATCH 044/159] Show proper post statuses in admin #639 --- classes/class-ep-api.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 03b23411d9..95f140c34e 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1638,9 +1638,29 @@ public function format_args( $args ) { $use_filters = true; } } else { + $statuses = get_post_stati( array( 'public' => true ) ); + + if ( is_admin() ) { + /** + * In the admin we will add protected and private post statuses to the default query + * per WP default behavior. + */ + $statuses = array_merge( $statuses, get_post_stati( array( 'protected' => true, 'show_in_admin_all_list' => true ) ) ); + + if ( is_user_logged_in() ) { + $statuses = array_merge( $statuses, get_post_stati( array( 'private' => true ) ) ); + } + } + + $post_status_filter_type = 'term'; + + if ( 1 < count( $statuses ) ) { + $post_status_filter_type = 'terms'; + } + $filter['bool']['must'][] = array( - 'term' => array( - 'post_status' => 'publish', + $post_status_filter_type => array( + 'post_status' => array_values( $statuses ), ), ); From 859abb496a9355d10dffda9379b530c633ba5973 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 4 Dec 2016 20:11:07 -0500 Subject: [PATCH 045/159] Rethink notifications to be more granular; handle feature auto-activation when requirement statuses change; show feature auto-activation notification; count intro as shown if someone just visits the settings page --- classes/class-ep-dashboard.php | 97 ++++++++++++++++------- classes/class-ep-features.php | 141 +++++++++++++++++++++++++++++++++ elasticpress.php | 13 +-- uninstall.php | 4 + 4 files changed, 223 insertions(+), 32 deletions(-) diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 9a3e9be552..42a5947e12 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -128,40 +128,60 @@ public function maybe_notice() { $options_host = get_option( 'ep_host' ); } - if ( empty( $host ) || ! ep_elasticsearch_can_connect() ) { - if ( $on_settings_page ) { - if ( false !== $options_host || ( defined( 'EP_HOST' ) && EP_HOST ) ) { - $notice = 'bad-host'; - } + $never_set_host = true; + if ( false !== $options_host || ( defined( 'EP_HOST' ) && EP_HOST ) ) { + $never_set_host = false; + } + + if ( ! $never_set_host ) { + /** + * Feature auto-activated sync notice check + */ + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $auto_activate_sync = get_site_option( 'ep_feature_auto_activated_sync', false ); } else { - $notice = 'bad-host'; + $auto_activate_sync = get_option( 'ep_feature_auto_activated_sync', false ); } - } - /** - * Never synced notice check - */ - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $last_sync = get_site_option( 'ep_last_sync', false ); - } else { - $last_sync = get_option( 'ep_last_sync', false ); - } + if ( ! empty( $auto_activate_sync ) && ! isset( $_GET['do_sync'] ) ) { + $notice = 'auto-activate-sync'; + } - if ( false === $last_sync && ! isset( $_GET['do_sync'] ) ) { - $notice = 'no-sync'; - } + /** + * Upgrade sync notice check + */ + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $need_upgrade_sync = get_site_option( 'ep_need_upgrade_sync', false ); + } else { + $need_upgrade_sync = get_option( 'ep_need_upgrade_sync', false ); + } - /** - * Upgrade sync notice check - */ - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $need_upgrade_sync = get_site_option( 'ep_need_upgrade_sync', false ); - } else { - $need_upgrade_sync = get_option( 'ep_need_upgrade_sync', false ); + if ( $need_upgrade_sync && ! isset( $_GET['do_sync'] ) ) { + $notice = 'upgrade-sync'; + } + + /** + * Never synced notice check + */ + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $last_sync = get_site_option( 'ep_last_sync', false ); + } else { + $last_sync = get_option( 'ep_last_sync', false ); + } + + if ( false === $last_sync && ! isset( $_GET['do_sync'] ) ) { + $notice = 'no-sync'; + } } - if ( $need_upgrade_sync && ! isset( $_GET['do_sync'] ) ) { - $notice = 'upgrade-sync'; + if ( empty( $host ) || ! ep_elasticsearch_can_connect() ) { + if ( $on_settings_page ) { + if ( ! $never_set_host ) { + $notice = 'bad-host'; + } + } else { + $notice = 'bad-host'; + } } /** @@ -233,6 +253,21 @@ public function maybe_notice() {
+
+

run a sync for it to work.', 'elasticpress' ), esc_html( $feature->title ), esc_url( $url ) ); ?>

+
+ registered_features as $slug => $feature ) { + $status = $feature->requirements_status(); + $requirement_statuses[ $slug ] = (int) $status->code; + } + + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_feature_requirement_statuses', $requirement_statuses ); + } else { + update_option( 'ep_feature_requirement_statuses', $requirement_statuses ); + } + } + + return $requirement_statuses; + } + + /** + * Activate or deactivate a feature + * + * @param string $slug + * @param boolean $active + * @since 2.2 + */ + public function activate_feature( $slug, $active = true ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $feature_settings = get_site_option( 'ep_feature_settings', array() ); + } else { + $feature_settings = get_option( 'ep_feature_settings', array() ); + } + + $feature = $this->registered_features[ $slug ]; + + $feature_settings[ $slug ] = ( ! empty( $feature->default_settings ) ) ? $feature->default_settings : array(); + $feature_settings[ $slug ]['active'] = (bool) $active; + + if ( $active ) { + $feature->post_activation(); + } + } + + /** + * When plugins are adjusted, we need to determine how to activate/deactivate features + * + * @since 2.2 + */ + public function maybe_activate_deactivate_feature( $plugin ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $old_requirement_statuses = get_site_option( 'ep_feature_requirement_statuses', false ); + } else { + $old_requirement_statuses = get_option( 'ep_feature_requirement_statuses', false ); + } + + $new_requirement_statuses = $this->get_requirement_statuses( true ); + + if ( false === $old_requirement_statuses ) { + // Really weird situation here. Let's just run the EP activation hook function + ep_on_activate(); + return; + } + + foreach ( $new_requirement_statuses as $slug => $code ) { + $feature = ep_get_registered_feature( $slug ); + + // This is a new feature + if ( ! empty( $old_requirement_statuses[ $slug ] ) ) { + if ( 0 === $code ) { + ep_activate_feature( $slug ); + + if ( $feature->requires_install_reindex ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); + } else { + update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); + } + } + } + } else { + // This feature has a 0 "ok" code when it did not before + if ( $old_requirement_statuses[ $slug ] !== $code && ( 0 === $code || 2 === $code ) ) { + $active = ( 0 === $code ); + + if ( ! $feature->is_active() && $active ) { + // Need to activate and maybe set a sync notice + if ( $feature->requires_install_reindex ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); + } else { + update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); + } + } + + ep_activate_feature( $slug, $active ); + } elseif ( $feature->is_active() && ! $active ) { + // Just deactivate + + ep_activate_feature( $slug, $active ); + } + } + } + } + } + /** * Set up all active features * @@ -109,6 +230,26 @@ function ep_register_feature( $slug, $feature_args ) { return EP_Features::factory()->register_feature( $slug, $feature_args ); } +/** + * Activate a feature + * + * @param string $slug + * @since 2.2 + */ +function ep_activate_feature( $slug ) { + EP_Features::factory()->activate_feature( $slug ); +} + +/** + * Dectivate a feature + * + * @param string $slug + * @since 2.2 + */ +function ep_deactivate_feature( $slug ) { + EP_Features::factory()->activate_feature( $slug, false ); +} + /** * Easy access function to get a EP_Feature object from a slug * @param string $slug diff --git a/elasticpress.php b/elasticpress.php index 9496da2d22..6b63edf5bf 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -69,21 +69,24 @@ function ep_on_activate() { } /** - * Run feature post activation hooks + * Activate necessary features if this is the first time activating + * the plugin. */ if ( false === $feature_settings ) { $registered_features = EP_Features::factory()->registered_features; foreach ( $registered_features as $slug => $feature ) { if ( 0 === $feature->requirements_status()->code ) { - $feature_settings[ $slug ] = ( ! empty( $feature->default_settings ) ) ? $feature->default_settings : array(); - $feature_settings[ $slug ]['active'] = true; - - $feature->post_activation(); + ep_activate_feature( $slug ); } } } + /** + * Cache requirement statuses so we can detect changes later + */ + EP_Features::factory()->get_requirement_statuses( true ); + /** * Reindex if we cross a reindex version in the upgrade */ diff --git a/uninstall.php b/uninstall.php index 7729c7d67d..40d76d7ee0 100644 --- a/uninstall.php +++ b/uninstall.php @@ -72,6 +72,10 @@ protected static function clean_options() { delete_site_option( 'ep_last_sync' ); delete_option( 'ep_need_upgrade_sync' ); delete_site_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_auto_activated_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); } /** From 5a619c559c850ac43877600f65f51de7701d624d Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 5 Dec 2016 00:10:16 -0500 Subject: [PATCH 046/159] Make notifications dismissible; rethink JS folder structure --- Gruntfile.js | 16 ++- assets/js/admin.min.js | 2 +- assets/js/dashboard.min.js | 1 + assets/js/src/admin.js | 21 ++++ assets/js/{admin.js => src/dashboard.js} | 44 ++++---- classes/class-ep-dashboard.php | 124 ++++++++++++++++++----- uninstall.php | 2 + 7 files changed, 154 insertions(+), 56 deletions(-) create mode 100644 assets/js/dashboard.min.js create mode 100644 assets/js/src/admin.js rename assets/js/{admin.js => src/dashboard.js} (88%) diff --git a/Gruntfile.js b/Gruntfile.js index 4689865ab5..284148821e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,7 +9,6 @@ module.exports = function ( grunt ) { uglify : { production : { - options : { beautify : false, preserveComments : false, @@ -19,11 +18,13 @@ module.exports = function ( grunt ) { }, files : { + 'assets/js/dashboard.min.js' : [ + 'assets/js/src/dashboard.js' + ], 'assets/js/admin.min.js' : [ - 'assets/js/admin.js' + 'assets/js/src/admin.js' ] } - } }, @@ -97,23 +98,18 @@ module.exports = function ( grunt ) { }, scripts : { - files : [ - 'assets/js/admin.js' + 'assets/js/src/*.js' ], - tasks : ['uglify:production'] }, styles : { - files : [ 'assets/css/*.scss' ], - tasks : ['sass', 'autoprefixer', 'cssmin'] - } } @@ -124,4 +120,4 @@ module.exports = function ( grunt ) { // A very basic default task. grunt.registerTask ( 'default', ['uglify:production', 'sass', 'autoprefixer', 'cssmin', 'makepot'] ); -}; \ No newline at end of file +}; diff --git a/assets/js/admin.min.js b/assets/js/admin.min.js index bce6a9592d..4c19f8bc7e 100644 --- a/assets/js/admin.min.js +++ b/assets/js/admin.min.js @@ -1 +1 @@ -!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=ep.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=ep.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=ep.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(ep.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null}else if("finished"===o){if(j.text(ep.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:ep.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",feature_sync:p,nonce:ep.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-features")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-feature"),e=g.find(".ep-feature-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_feature",feature:c,nonce:ep.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("feature-active"):e.removeClass("feature-active"),a.data.reindex&&(o="sync",e.addClass("feature-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("feature-active"),e.removeClass("feature-syncing")},700)})}),ep.index_meta?ep.index_meta.wpcli?(o="wpcli",b()):(q=ep.index_meta.offset,r=ep.index_meta.found_posts,ep.index_meta.feature_sync&&(p=ep.index_meta.feature_sync),ep.index_meta.current_site&&(e=ep.index_meta.current_site),ep.index_meta.site_stack&&(f=ep.index_meta.site_stack),f&&f.length?ep.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):0!==r||ep.index_meta.start?ep.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):(o="finished",b())):ep.auto_start_index&&(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file +!function(a){"use strict";a(".notice").on("click",".notice-dismiss",function(b){var c=b.delegateTarget.getAttribute("data-ep-notice");c&&a.ajax({method:"post",data:{nonce:epAdmin.nonce,action:"ep_notice_dismiss",notice:c},url:ajaxurl})})}(jQuery); \ No newline at end of file diff --git a/assets/js/dashboard.min.js b/assets/js/dashboard.min.js new file mode 100644 index 0000000000..68f9660367 --- /dev/null +++ b/assets/js/dashboard.min.js @@ -0,0 +1 @@ +!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=epDash.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=epDash.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=epDash.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(epDash.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null}else if("finished"===o){if(j.text(epDash.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:epDash.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",feature_sync:p,nonce:epDash.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-features")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-feature"),e=g.find(".ep-feature-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_feature",feature:c,nonce:epDash.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("feature-active"):e.removeClass("feature-active"),a.data.reindex&&(o="sync",e.addClass("feature-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("feature-active"),e.removeClass("feature-syncing")},700)})}),epDash.index_meta?epDash.index_meta.wpcli?(o="wpcli",b()):(q=epDash.index_meta.offset,r=epDash.index_meta.found_posts,epDash.index_meta.feature_sync&&(p=epDash.index_meta.feature_sync),epDash.index_meta.current_site&&(e=epDash.index_meta.current_site),epDash.index_meta.site_stack&&(f=epDash.index_meta.site_stack),f&&f.length?epDash.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):0!==r||epDash.index_meta.start?epDash.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):(o="finished",b())):epDash.auto_start_index&&(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()),k.on("click",function(){o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file diff --git a/assets/js/src/admin.js b/assets/js/src/admin.js new file mode 100644 index 0000000000..318aa63c3b --- /dev/null +++ b/assets/js/src/admin.js @@ -0,0 +1,21 @@ +( function( $ ) { + 'use strict'; + + $( '.notice' ).on( 'click', '.notice-dismiss', function( event ) { + var notice = event.delegateTarget.getAttribute( 'data-ep-notice' ); + if ( ! notice ) { + return; + } + + $.ajax( { + method: 'post', + data: { + nonce: epAdmin.nonce, + action: 'ep_notice_dismiss', + notice: notice + }, + url: ajaxurl + } ); + } ); + +} )( jQuery ); diff --git a/assets/js/admin.js b/assets/js/src/dashboard.js similarity index 88% rename from assets/js/admin.js rename to assets/js/src/dashboard.js index 261e12b696..7785eef6f8 100644 --- a/assets/js/admin.js +++ b/assets/js/src/dashboard.js @@ -56,7 +56,7 @@ data: { action: 'ep_save_feature', feature: feature, - nonce: ep.nonce, + nonce: epDash.nonce, settings: settings } } ).done( function( response ) { @@ -88,29 +88,29 @@ } ); } ); - if ( ep.index_meta ) { - if ( ep.index_meta.wpcli ) { + if ( epDash.index_meta ) { + if ( epDash.index_meta.wpcli ) { syncStatus = 'wpcli'; updateSyncDash(); } else { - processed = ep.index_meta.offset; - toProcess = ep.index_meta['found_posts']; + processed = epDash.index_meta.offset; + toProcess = epDash.index_meta['found_posts']; - if ( ep.index_meta.feature_sync ) { - featureSync = ep.index_meta.feature_sync; + if ( epDash.index_meta.feature_sync ) { + featureSync = epDash.index_meta.feature_sync; } - if ( ep.index_meta.current_site ) { - currentSite = ep.index_meta.current_site; + if ( epDash.index_meta.current_site ) { + currentSite = epDash.index_meta.current_site; } - if ( ep.index_meta.site_stack ) { - siteStack = ep.index_meta.site_stack; + if ( epDash.index_meta.site_stack ) { + siteStack = epDash.index_meta.site_stack; } if ( siteStack && siteStack.length ) { // We are mid sync - if ( ep.auto_start_index ) { + if ( epDash.auto_start_index ) { syncStatus = 'sync'; history.pushState( {}, document.title, document.location.pathname + document.location.search.replace( /&do_sync/, '' ) ); @@ -122,13 +122,13 @@ updateSyncDash(); } } else { - if ( 0 === toProcess && ! ep.index_meta.start ) { + if ( 0 === toProcess && ! epDash.index_meta.start ) { // Sync finished syncStatus = 'finished'; updateSyncDash(); } else { // We are mid sync - if ( ep.auto_start_index ) { + if ( epDash.auto_start_index ) { syncStatus = 'sync'; history.pushState( {}, document.title, document.location.pathname + document.location.search.replace( /&do_sync/, '' ) ); @@ -144,7 +144,7 @@ } } else { // Start a new sync automatically - if ( ep.auto_start_index ) { + if ( epDash.auto_start_index ) { syncStatus = 'sync'; history.pushState( {}, document.title, document.location.pathname + document.location.search.replace( /&do_sync/, '' ) ); @@ -163,7 +163,7 @@ } if ( 'sync' === syncStatus ) { - var text = ep.sync_syncing + ' ' + parseInt( processed ) + '/' + parseInt( toProcess ); + var text = epDash.sync_syncing + ' ' + parseInt( processed ) + '/' + parseInt( toProcess ); if ( currentSite ) { text += ' (' + currentSite.url + ')' @@ -180,7 +180,7 @@ $resumeSyncButton.hide(); $startSyncButton.hide(); } else if ( 'pause' === syncStatus ) { - var text = ep.sync_paused + ' ' + parseInt( processed ) + '/' + parseInt( toProcess ); + var text = epDash.sync_paused + ' ' + parseInt( processed ) + '/' + parseInt( toProcess ); if ( currentSite ) { text += ' (' + currentSite.url + ')' @@ -197,7 +197,7 @@ $resumeSyncButton.show(); $startSyncButton.hide(); } else if ( 'wpcli' === syncStatus ) { - var text = ep.sync_wpcli; + var text = epDash.sync_wpcli; $syncStatusText.text( text ); @@ -210,7 +210,7 @@ $resumeSyncButton.hide(); $startSyncButton.hide(); } else if ( 'error' === syncStatus ) { - $syncStatusText.text( ep.sync_error ); + $syncStatusText.text( epDash.sync_error ); $syncStatusText.show(); $startSyncButton.show(); $cancelSyncButton.hide(); @@ -246,7 +246,7 @@ featureSync = null; } else if ( 'finished' === syncStatus ) { - $syncStatusText.text( ep.sync_complete ); + $syncStatusText.text( epDash.sync_complete ); $syncStatusText.show(); $progressBar.hide(); @@ -275,7 +275,7 @@ url: ajaxurl, data: { action: 'ep_cancel_index', - nonce: ep.nonce + nonce: epDash.nonce } } ); } @@ -287,7 +287,7 @@ data: { action: 'ep_index', feature_sync: featureSync, - nonce: ep.nonce + nonce: epDash.nonce } } ).done( function( response ) { if ( 'sync' !== syncStatus ) { diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 42a5947e12..bf503d38ca 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -37,10 +37,12 @@ public function setup() { } add_action( 'wp_ajax_ep_save_feature', array( $this, 'action_wp_ajax_ep_save_feature' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_dashboard_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_admin_scripts' ) ); add_action( 'admin_init', array( $this, 'action_admin_init' ) ); add_action( 'admin_init', array( $this, 'intro_or_dashboard' ) ); add_action( 'wp_ajax_ep_index', array( $this, 'action_wp_ajax_ep_index' ) ); + add_action( 'wp_ajax_ep_notice_dismiss', array( $this, 'action_wp_ajax_ep_notice_dismiss' ) ); add_action( 'wp_ajax_ep_cancel_index', array( $this, 'action_wp_ajax_ep_cancel_index' ) ); add_action( 'admin_notices', array( $this, 'maybe_notice' ) ); add_action( 'network_admin_notices', array( $this, 'maybe_notice' ) ); @@ -190,11 +192,13 @@ public function maybe_notice() { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $intro_shown = get_site_option( 'ep_intro_shown', false ); + $skip_intro_shown_notice = get_site_option( 'ep_hide_intro_shown_notice', false ); } else { $intro_shown = get_option( 'ep_intro_shown', false ); + $skip_intro_shown_notice = get_option( 'ep_hide_intro_shown_notice', false ); } - if ( ! $intro_shown && false === $options_host && ( ! defined( 'EP_HOST' ) || ! EP_HOST ) ) { + if ( ! $skip_intro_shown_notice && ! $intro_shown && false === $options_host && ( ! defined( 'EP_HOST' ) || ! EP_HOST ) ) { $notice = 'need-setup'; } @@ -222,7 +226,7 @@ public function maybe_notice() { } ?> -
+

quick set up process to get the plugin working.', 'elasticpress' ), esc_url( $url ) ); ?>

-
+

sync to get the plugin working.', 'elasticpress' ), esc_url( $url ) ); ?>

-
+

run a sync.', 'elasticpress' ), esc_url( $url ) ); ?>

-
+

run a sync for it to work.', 'elasticpress' ), esc_html( $feature->title ), esc_url( $url ) ); ?>

id ) && strpos( get_current_screen()->id, 'elasticpress' ) !== false ) { - $maybe_min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - - wp_enqueue_style( 'ep_admin_styles', EP_URL . 'assets/css/admin' . $maybe_min . '.css', array(), EP_VERSION ); + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + wp_enqueue_style( 'ep_admin_styles', EP_URL . 'assets/css/admin.css', array(), EP_VERSION ); + } else { + wp_enqueue_style( 'ep_admin_styles', EP_URL . 'assets/css/admin.min.css', array(), EP_VERSION ); + } if ( ! empty( $_GET['page'] ) && ( 'elasticpress' === $_GET['page'] || 'elasticpress-settings' === $_GET['page'] ) ) { - wp_enqueue_script( 'ep_admin_scripts', EP_URL . 'assets/js/admin' . $maybe_min . '.js', array( 'jquery' ), EP_VERSION, true ); + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + wp_enqueue_script( 'ep_dashboard_scripts', EP_URL . 'assets/js/src/dashboard.js', array( 'jquery' ), EP_VERSION, true ); + } else { + wp_enqueue_script( 'ep_dashboard_scripts', EP_URL . 'assets/js/dashboard.min.js', array( 'jquery' ), EP_VERSION, true ); + } - $data = array( 'nonce' => wp_create_nonce( 'ep_nonce' ) ); + $data = array( 'nonce' => wp_create_nonce( 'ep_dashboard_nonce' ) ); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $index_meta = get_site_option( 'ep_index_meta' ); @@ -569,11 +630,28 @@ public function action_admin_enqueue_scripts() { $data['sync_wpcli'] = esc_html__( "WP CLI sync is occuring. Refresh the page to see if it's finished", 'elasticpress' ); $data['sync_error'] = esc_html__( 'An error occured while syncing', 'elasticpress' ); - wp_localize_script( 'ep_admin_scripts', 'ep', $data ); + wp_localize_script( 'ep_dashboard_scripts', 'epDash', $data ); } } } + /** + * Enqueue scripts to be used across all of WP admin + * + * @since 2.2 + */ + public function action_admin_enqueue_admin_scripts() { + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + wp_enqueue_script( 'ep_admin_scripts', EP_URL . 'assets/js/src/admin.js', array( 'jquery' ), EP_VERSION, true ); + } else { + wp_enqueue_script( 'ep_admin_scripts', EP_URL . 'assets/js/admin.min.js', array( 'jquery' ), EP_VERSION, true ); + } + + wp_localize_script( 'ep_admin_scripts', 'epAdmin', array( + 'nonce' => wp_create_nonce( 'ep_admin_nonce' ), + ) ); + } + /** * Admin-init actions * diff --git a/uninstall.php b/uninstall.php index 40d76d7ee0..94839f8d08 100644 --- a/uninstall.php +++ b/uninstall.php @@ -76,6 +76,8 @@ protected static function clean_options() { delete_site_option( 'ep_feature_requirement_statuses' ); delete_option( 'ep_feature_auto_activated_sync' ); delete_site_option( 'ep_feature_auto_activated_sync' ); + delete_option( 'ep_hide_intro_shown_notice' ); + delete_site_option( 'ep_hide_intro_shown_notice' ); } /** From 304b305c0eb7d14a67a565cccddd324901cede90 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 5 Dec 2016 00:49:56 -0500 Subject: [PATCH 047/159] Properly activate features --- classes/class-ep-features.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index 6602c4a507..246e3e6fcb 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -118,6 +118,12 @@ public function activate_feature( $slug, $active = true ) { $feature_settings[ $slug ] = ( ! empty( $feature->default_settings ) ) ? $feature->default_settings : array(); $feature_settings[ $slug ]['active'] = (bool) $active; + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_feature_settings', $feature_settings ); + } else { + update_option( 'ep_feature_settings', $feature_settings ); + } + if ( $active ) { $feature->post_activation(); } From d5c0f877656d00a9f287acef4d44b8fb91553446 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Tue, 6 Dec 2016 01:03:10 +0530 Subject: [PATCH 048/159] Strip whitespace from post_status in EP request params --- classes/class-ep-api.php | 1 + 1 file changed, 1 insertion(+) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index dba213af6e..3530ddcd0d 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1623,6 +1623,7 @@ public function format_args( $args ) { // should NEVER be "any" but just in case if ( 'any' !== $args['post_status'] ) { $post_status = (array) ( is_string( $args['post_status'] ) ? explode( ',', $args['post_status'] ) : $args['post_status'] ); + $post_status = array_map( 'trim', $post_status ); $terms_map_name = 'terms'; if ( count( $post_status ) < 2 ) { $terms_map_name = 'term'; From 18f16b1a2b6265c88fef1a66af67a4ab92817817 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Tue, 6 Dec 2016 02:45:53 +0530 Subject: [PATCH 049/159] Fix CLI commands to list/activate/deactivate features --- bin/wp-cli.php | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index 4d2d9f9471..e205d4d9db 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -61,9 +61,9 @@ public function activate_feature( $args, $assoc_args ) { } if ( ! empty( $assoc_args['network-wide'] ) ) { - $active_features = get_site_option( 'ep_active_features', array() ); + $active_features = get_site_option( 'ep_feature_settings', array() ); } else { - $active_features = get_option( 'ep_active_features', array() ); + $active_features = get_option( 'ep_feature_settings', array() ); } if ( $feature->is_active() ) { @@ -78,8 +78,9 @@ public function activate_feature( $args, $assoc_args ) { WP_CLI::warning( printf( __( 'Feature is usable but there are warnings: %s', 'elasticpress' ), $status->message ) ); } - $active_features[] = $feature->slug; - + $active_features[ $feature->slug ] = wp_parse_args( $active_features[ $feature->slug ], $feature->default_settings ); + $active_features[ $feature->slug ]['active'] = true; + $feature->post_activation(); if ( $feature->requires_install_reindex ) { @@ -87,9 +88,9 @@ public function activate_feature( $args, $assoc_args ) { } if ( ! empty( $assoc_args['network-wide'] ) ) { - update_site_option( 'ep_active_features', $active_features ); + update_site_option( 'ep_feature_settings', $active_features ); } else { - update_option( 'ep_active_features', $active_features ); + update_option( 'ep_feature_settings', $active_features ); } WP_CLI::success( __( 'Feature activated', 'elasticpress' ) ); @@ -112,23 +113,23 @@ public function deactivate_feature( $args, $assoc_args ) { } if ( ! empty( $assoc_args['network-wide'] ) ) { - $active_features = get_site_option( 'ep_active_features', array() ); + $active_features = get_site_option( 'ep_feature_settings', array() ); } else { - $active_features = get_option( 'ep_active_features', array() ); + $active_features = get_option( 'ep_feature_settings', array() ); } - $key = array_search( $feature->slug, $active_features ); + $key = array_search( $feature->slug, array_keys( $active_features ) ); - if ( false !== $key ) { - unset( $active_features[$key] ); + if ( false !== $key && $active_features[ $feature->slug ]['active'] ) { + $active_features[ $feature->slug ]['active'] = false; } else { WP_CLI::error( __( 'Feature is not active', 'elasticpress' ) ); } if ( ! empty( $assoc_args['network-wide'] ) ) { - update_site_option( 'ep_active_features', $active_features ); + update_site_option( 'ep_feature_settings', $active_features ); } else { - update_option( 'ep_active_features', $active_features ); + update_option( 'ep_feature_settings', $active_features ); } WP_CLI::success( __( 'Feature deactivated', 'elasticpress' ) ); @@ -147,19 +148,24 @@ public function list_features( $args, $assoc_args ) { if ( empty( $assoc_args['all'] ) ) { if ( ! empty( $assoc_args['network-wide'] ) ) { - $features = get_site_option( 'ep_active_features', array() ); + $features = get_site_option( 'ep_feature_settings', array() ); } else { - $features = get_option( 'ep_active_features', array() ); + $features = get_option( 'ep_feature_settings', array() ); } - WP_CLI::line( __( 'Active features:', 'elasticpress' ) ); + + foreach ( $features as $key => $feature ) { + if( $feature['active'] ) { + WP_CLI::line( $key ); + } + } } else { WP_CLI::line( __( 'Registered features:', 'elasticpress' ) ); $features = wp_list_pluck( EP_Features::factory()->registered_features, 'slug' ); - } - - foreach ( $features as $feature ) { - WP_CLI::line( $feature ); + + foreach ( $features as $feature ) { + WP_CLI::line( $feature ); + } } } From 73c485f1107c79b66dd48ed3de1b36ad238a6fb8 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 19:09:48 +0000 Subject: [PATCH 050/159] Clean up related posts tests --- tests/features/test-related-posts.php | 84 ++------------------------- 1 file changed, 4 insertions(+), 80 deletions(-) diff --git a/tests/features/test-related-posts.php b/tests/features/test-related-posts.php index 779a27913a..1bc1a273e1 100644 --- a/tests/features/test-related-posts.php +++ b/tests/features/test-related-posts.php @@ -31,7 +31,6 @@ public function setUp() { * Clean up after each test. Reset our mocks * * @since 2.1 - * @group related_posts */ public function tearDown() { parent::tearDown(); @@ -45,88 +44,15 @@ public function tearDown() { * Log action usage for tests * * @since 2.1 - * @group related_posts */ public function action_ep_related_html_attached() { $this->fired_actions['ep_related_html_attached'] = true; } - - /** - * Test that related posts is off - * - * @since 2.1 - * @group related_posts - */ - public function testRelatedPostsOff() { - delete_option( 'ep_active_features' ); - - $post_ids = array(); - - ep_create_and_sync_post(); - - ep_refresh_index(); - - add_action( 'ep_related_html_attached', array( $this, 'action_ep_related_html_attached' ), 10, 0 ); - - $args = array( - 'ep_integrate' => true, - ); - - $query = new WP_Query( $args ); - - ob_start(); - if ( $query->have_posts() ) { - while ( $query->have_posts() ) { - $query->the_post(); - - the_content(); - } - } - ob_get_clean(); - - $this->assertTrue( empty( $this->fired_actions['ep_related_html_attached'] ) ); - } - - /** - * Test that related posts is on - * - * @since 2.1 - * @group related_posts - */ - public function testRelatedPostsOn() { - $post_ids = array(); - - ep_create_and_sync_post(); - - ep_refresh_index(); - - ep_activate_feature( 'related_posts' ); - - EP_Features::factory()->setup_features(); - - add_action( 'ep_related_html_attached', array( $this, 'action_ep_related_html_attached' ), 10 ); - - $args = array( - 'ep_integrate' => true, - ); - - $query = new WP_Query( $args ); - - ob_start(); - if ( $query->have_posts() ) { - while ( $query->have_posts() ) { - $query->the_post(); - - the_content(); - } - } - ob_get_clean(); - - $this->assertTrue( ! empty( $this->fired_actions['ep_related_html_attached'] ) ); - } /** * Test for related post args filter + * + * @group related_posts */ public function testFindRelatedPostFilter(){ $post_id = ep_create_and_sync_post( array( 'post_content' => 'findme test 1' ) ); @@ -143,14 +69,12 @@ public function testFindRelatedPostFilter(){ $related = ep_find_related( $post_id ); $this->assertEquals( 2, sizeof( $related ) ); remove_filter( 'ep_find_related_args', array( $this, 'find_related_posts_filter' ) ); - - $related = ep_find_related( $post_id ); - $this->assertEquals( 3, sizeof( $related ) ); } /** + * Detect EP fire + * * @param $args - * * @return mixed */ public function find_related_posts_filter( $args ){ From fb0131e17e46aee0fdde03d6f62513ce6fb0863d Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 19:26:27 +0000 Subject: [PATCH 051/159] Fix singular post status search --- classes/class-ep-api.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index a57d6eae1c..2e14c2b37a 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1653,15 +1653,18 @@ public function format_args( $args ) { } } - $post_status_filter_type = 'term'; + $statuses = array_values( $statuses ); - if ( 1 < count( $statuses ) ) { - $post_status_filter_type = 'terms'; + $post_status_filter_type = 'terms'; + + if ( 1 === count( $statuses ) ) { + $post_status_filter_type = 'term'; + $statuses = $statuses[0]; } $filter['bool']['must'][] = array( $post_status_filter_type => array( - 'post_status' => array_values( $statuses ), + 'post_status' => $statuses, ), ); From c022c198e5910945879834ad6d746a8830411fad Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 19:28:45 +0000 Subject: [PATCH 052/159] Search test clean up --- tests/features/test-search.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/features/test-search.php b/tests/features/test-search.php index 16a043c814..b44cab7983 100644 --- a/tests/features/test-search.php +++ b/tests/features/test-search.php @@ -6,7 +6,6 @@ class EPTestSearchFeature extends EP_Test_Base { * Setup each test. * * @since 2.1 - * @group search */ public function setUp() { global $wpdb; @@ -31,7 +30,6 @@ public function setUp() { * Clean up after each test. Reset our mocks * * @since 2.1 - * @group search */ public function tearDown() { parent::tearDown(); @@ -99,6 +97,8 @@ public function testSearchOn() { /** * Test case for when index is deleted, request for Elasticsearch should fall back to WP Query + * + * @group search */ public function testSearchIndexDeleted(){ global $wpdb; From 2dac03543fbf902c5128706bc5d3efb26563dc8d Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 19:48:29 +0000 Subject: [PATCH 053/159] Fix up related posts tests; specifying no post type on the front end defaults to post type --- tests/features/test-related-posts.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/features/test-related-posts.php b/tests/features/test-related-posts.php index 1bc1a273e1..40390bd9f0 100644 --- a/tests/features/test-related-posts.php +++ b/tests/features/test-related-posts.php @@ -64,11 +64,14 @@ public function testFindRelatedPostFilter(){ ep_activate_feature( 'related_posts' ); EP_Features::factory()->setup_features(); - + + $related = ep_find_related( $post_id ); + $this->assertEquals( 1, sizeof( $related ) ); + add_filter( 'ep_find_related_args', array( $this, 'find_related_posts_filter' ), 10, 1 ); $related = ep_find_related( $post_id ); $this->assertEquals( 2, sizeof( $related ) ); - remove_filter( 'ep_find_related_args', array( $this, 'find_related_posts_filter' ) ); + remove_filter( 'ep_find_related_args', array( $this, 'find_related_posts_filter' ), 10, 1 ); } /** @@ -78,7 +81,7 @@ public function testFindRelatedPostFilter(){ * @return mixed */ public function find_related_posts_filter( $args ){ - $args['post_type'] = 'post'; + $args['post_type'] = array( 'post', 'page' ); return $args; } From 2efbfccbbb64c8e54d483a627d55979dacb1f9fc Mon Sep 17 00:00:00 2001 From: Kristoffer Svanmark Date: Wed, 7 Dec 2016 20:57:59 +0100 Subject: [PATCH 054/159] Reverse refactoring --- classes/class-ep-sync-manager.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index 33abce94f3..c72de726cb 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -186,7 +186,7 @@ public function action_sync_on_update( $post_ID ) { $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $post_ID ); - if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || wp_is_post_revision( $post_ID ) ) { + if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || 'revision' === $post_type ) { // Bypass saving if doing autosave or post type is revision return; } @@ -198,6 +198,13 @@ public function action_sync_on_update( $post_ID ) { } } + $post = get_post( $post_ID ); + + // If the post is an auto-draft - let's abort. + if ( 'auto-draft' == $post->post_status ) { + return; + } + // If post is indexable go ahead and index it // If post not indexable, remove it's existance from index if ( $this->is_post_indexable( $post_ID ) ) { @@ -220,13 +227,7 @@ public function is_post_indexable( $post_ID ) { $post_type = get_post_type( $post_ID ); $post_status = get_post_status( $post_ID ); - if ( - ! in_array( $post_type, $indexable_post_types ) - || - ! in_array( $post_status, $indexable_post_statuses ) - || - $post_status == 'auto-draft' - ) { + if ( ! in_array( $post_type, $indexable_post_types ) || ! in_array( $post_status, $indexable_post_statuses ) ) { return false; } From 3e5521bd351e21b84034fc14fda35daa4cf7058c Mon Sep 17 00:00:00 2001 From: Kristoffer Svanmark Date: Wed, 7 Dec 2016 20:58:28 +0100 Subject: [PATCH 055/159] Revert "#613 - Introduce new method to check if a post is indexable in action_sync_on_update" This reverts commit c59c54b18c42a23ab748e3cc6815549a053894dd. --- classes/class-ep-sync-manager.php | 46 ++++++++++++------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index c72de726cb..7741f78a38 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -21,7 +21,7 @@ public function __construct() { } /** * Save posts for indexing later - * + * * @since 2.0 * @var array */ @@ -45,7 +45,7 @@ public function setup() { add_action( 'added_post_meta', array( $this, 'action_queue_meta_sync' ), 10, 4 ); add_action( 'shutdown', array( $this, 'action_index_sync_queue' ) ); } - + /** * Remove actions and filters * @@ -84,7 +84,7 @@ public function action_index_sync_queue() { /** * When whitelisted meta is updated, queue the post for reindex - * + * * @param int $meta_id * @param int $object_id * @param string $meta_key @@ -102,7 +102,7 @@ public function action_queue_meta_sync( $meta_id, $object_id, $meta_key, $meta_v if ( ! empty( $importer ) ) { return; } - + $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $object_id ); @@ -182,7 +182,7 @@ public function action_sync_on_update( $post_ID ) { if ( ! empty( $importer ) ) { return; } - + $indexable_post_statuses = ep_get_indexable_post_status(); $post_type = get_post_type( $post_ID ); @@ -205,35 +205,23 @@ public function action_sync_on_update( $post_ID ) { return; } - // If post is indexable go ahead and index it - // If post not indexable, remove it's existance from index - if ( $this->is_post_indexable( $post_ID ) ) { - do_action( 'ep_sync_on_transition', $post_ID ); - $this->sync_post( $post_ID, false ); - } else { + // Our post was published, but is no longer, so let's remove it from the Elasticsearch index + if ( ! in_array( $post->post_status, $indexable_post_statuses ) ) { $this->action_delete_post( $post_ID ); - } - } + } else { + $post_type = get_post_type( $post_ID ); - /** - * Check if post matches the criterias to be indexed - * - * @param int $post_ID The post id to check - * @return boolean - */ - public function is_post_indexable( $post_ID ) { - $indexable_post_types = ep_get_indexable_post_types(); - $indexable_post_statuses = ep_get_indexable_post_status(); - $post_type = get_post_type( $post_ID ); - $post_status = get_post_status( $post_ID ); + $indexable_post_types = ep_get_indexable_post_types(); - if ( ! in_array( $post_type, $indexable_post_types ) || ! in_array( $post_status, $indexable_post_statuses ) ) { - return false; - } + if ( in_array( $post_type, $indexable_post_types ) ) { - return true; - } + do_action( 'ep_sync_on_transition', $post_ID ); + $this->sync_post( $post_ID, false ); + } + } + } + /** * Return a singleton instance of the current class * From 905483df1dbed479575bf535c1c685e5ab78b26c Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 19:59:21 +0000 Subject: [PATCH 056/159] Only integrate WC qsearch queries on front end when searching supported post type; refresh in WC test to let shard start --- features/woocommerce/woocommerce.php | 12 ++++++------ tests/features/test-woocommerce.php | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/features/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php index 9b7a7b0542..620cdd59b1 100644 --- a/features/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -303,7 +303,7 @@ function ep_wc_translate_args( $query ) { /** * If we have a WooCommerce specific query, lets hook it to ElasticPress and make the query ElasticSearch friendly */ - if ( $integrate || $query->is_search() ) { + if ( $integrate ) { // Handles the WC Top Rated Widget if ( has_filter( 'posts_clauses', array( WC()->query, 'order_by_rating_post_clauses' ) ) ) { @@ -350,10 +350,10 @@ function ep_wc_translate_args( $query ) { $s = $query->get( 's' ); - if ( empty( $s ) ) { - $query->query_vars['ep_integrate'] = true; - $query->query['ep_integrate'] = true; - } else { + $query->query_vars['ep_integrate'] = true; + $query->query['ep_integrate'] = true; + + if ( ! empty( $s ) ) { $query->set( 'orderby', false ); // Just order by relevance. // Search query @@ -384,7 +384,7 @@ function ep_wc_translate_args( $query ) { ) ) ); $query->set( 'search_fields', $search_fields ); - } elseif ( empty( $post_type ) || 'product' === $post_type ) { + } elseif ( 'product' === $post_type ) { $search_fields = $query->get( 'search_fields', array( 'post_title', 'post_content', 'post_excerpt' ) ); // Make sure we search skus on the front end diff --git a/tests/features/test-woocommerce.php b/tests/features/test-woocommerce.php index 9beeacf9d5..4ee68ae82f 100644 --- a/tests/features/test-woocommerce.php +++ b/tests/features/test-woocommerce.php @@ -184,7 +184,7 @@ public function testSearchOnShopOrderAdmin() { } /** - * Test search integration is on in general + * Test search integration is on in general for product searches * * @since 2.1 * @group woocommerce @@ -193,10 +193,13 @@ public function testSearchOnAllFrontEnd() { ep_activate_feature( 'woocommerce' ); EP_Features::factory()->setup_features(); + ep_refresh_index(); + add_action( 'ep_wp_query_search', array( $this, 'action_wp_query_search' ), 10, 0 ); $args = array( 's' => 'findme', + 'post_type' => 'product', ); $query = new WP_Query( $args ); From 3a75e99c45362a465d42d55b2303473adf318967 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 20:15:30 +0000 Subject: [PATCH 057/159] Fix multisite not post type test - no behavior defaults to post --- tests/test-multisite.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test-multisite.php b/tests/test-multisite.php index a3a9136bd4..7c2d3a7328 100644 --- a/tests/test-multisite.php +++ b/tests/test-multisite.php @@ -1026,7 +1026,7 @@ public function testNoPostTypeSearchQuery() { } /** - * Test a post type query non-search where no post type is specified + * Test a post type query non-search where no post type is specified. Defaults to `post` post type * * @since 1.3 */ @@ -1058,8 +1058,8 @@ public function testNoPostTypeNoSearchQuery() { $query = new WP_Query( $args ); - $this->assertEquals( $query->post_count, 5 ); - $this->assertEquals( $query->found_posts, 5 ); + $this->assertEquals( $query->post_count, 2 ); + $this->assertEquals( $query->found_posts, 2 ); } /** From 5499a50f9dc9d38e9938b78112b53007506d248c Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 20:29:05 +0000 Subject: [PATCH 058/159] Fix tax OR relation per #633 --- classes/class-ep-api.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 2e14c2b37a..eb66e16260 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1062,24 +1062,19 @@ public function format_args( $args ) { } } - /** - * Todo: This needs to be fixed - */ if ( ! empty( $tax_filter ) ) { $relation = 'must'; if ( ! empty( $args['tax_query']['relation'] ) && 'or' === strtolower( $args['tax_query']['relation'] ) ) { $relation = 'should'; } + + $es_tax_query[$relation] = $tax_filter; } if ( ! empty( $tax_must_not_filter ) ) { $es_tax_query['must_not'] = $tax_must_not_filter; } - - if ( ! empty( $tax_filter ) ) { - $es_tax_query['must'] = $tax_filter; - } if( ! empty( $es_tax_query ) ) { $filter['bool']['must'][]['bool'] = $es_tax_query; From 69067ebde380816fd88a4fcb4cb43d9196294039 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 20:30:14 +0000 Subject: [PATCH 059/159] Fix single site no post type test - new behavior defaults to post --- tests/test-single-site.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index b646147ada..73925cea1c 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -1213,7 +1213,7 @@ public function testAttachmentQuery() { } /** - * Test a query with no post type on non-search query + * Test a query with no post type on non-search query. Should default to `post` post type * * @since 1.3 */ @@ -1231,8 +1231,8 @@ public function testNoPostTypeNonSearchQuery() { $query = new WP_Query( $args ); - $this->assertEquals( 3, $query->post_count ); - $this->assertEquals( 3, $query->found_posts ); + $this->assertEquals( 2, $query->post_count ); + $this->assertEquals( 2, $query->found_posts ); } /** From 2c1d9976decba487b933170a01c9889009368e78 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 21:39:50 +0000 Subject: [PATCH 060/159] Rethink auto activation and upgrading per #633 --- classes/class-ep-features.php | 37 ++++++++++++++ elasticpress.php | 95 ++++++++++++++--------------------- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index 246e3e6fcb..70b194a8a2 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -27,6 +27,7 @@ class EP_Features { */ public function setup() { add_action( 'plugins_loaded', array( $this, 'setup_features' ) ); + add_action( 'plugins_loaded', array( $this, 'auto_activate_features' ) ); add_action( 'plugins_loaded', array( $this, 'maybe_activate_deactivate_feature' ) ); } @@ -99,6 +100,42 @@ public function get_requirement_statuses( $force = false ) { return $requirement_statuses; } + /** + * Auto activate features + * + * @since 2.2 + */ + public function auto_activate_features() { + if ( ! is_admin() || defined( 'DOING_AJAX' ) ) { + return; + } + + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $feature_settings = get_site_option( 'ep_feature_settings', false ); + } else { + $feature_settings = get_option( 'ep_feature_settings', false ); + } + + /** + * Activate necessary features if this is the first time activating + * the plugin. + */ + if ( false === $feature_settings ) { + $registered_features = $this->registered_features; + + foreach ( $registered_features as $slug => $feature ) { + if ( 0 === $feature->requirements_status()->code ) { + ep_activate_feature( $slug ); + } + } + } + + /** + * Cache requirement statuses so we can detect changes later + */ + $this->get_requirement_statuses( true ); + } + /** * Activate or deactivate a feature * diff --git a/elasticpress.php b/elasticpress.php index 6b63edf5bf..fe02678b34 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -55,81 +55,62 @@ } /** - * On activate, all features that meet their requirements with no warnings should be activated. + * Handle upgrades * - * @since 2.1 + * @since 2.2 */ -function ep_on_activate() { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $feature_settings = get_site_option( 'ep_feature_settings', false ); - $old_version = get_site_option( 'ep_version', false ); - } else { - $feature_settings = get_option( 'ep_feature_settings', false ); - $old_version = get_option( 'ep_version', false ); - } +function ep_handle_upgrades() { + if ( ! is_admin() || defined( 'DOING_AJAX' ) ) { + return; + } - /** - * Activate necessary features if this is the first time activating - * the plugin. - */ - if ( false === $feature_settings ) { - $registered_features = EP_Features::factory()->registered_features; - - foreach ( $registered_features as $slug => $feature ) { - if ( 0 === $feature->requirements_status()->code ) { - ep_activate_feature( $slug ); - } + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $old_version = get_site_option( 'ep_version', false ); + } else { + $old_version = get_option( 'ep_version', false ); } - } - /** - * Cache requirement statuses so we can detect changes later - */ - EP_Features::factory()->get_requirement_statuses( true ); + /** + * Reindex if we cross a reindex version in the upgrade + */ + $reindex_versions = apply_filters( 'ep_reindex_versions', array( + '2.2', + ) ); - /** - * Reindex if we cross a reindex version in the upgrade - */ - $reindex_versions = apply_filters( 'ep_reindex_versions', array( - '2.2', - ) ); + $need_upgrade_sync = false; - $need_upgrade_sync = false; + if ( false === $old_version ) { + $need_upgrade_sync = true; + } else { + $last_reindex_version = $reindex_versions[ count( $reindex_versions ) - 1 ]; - if ( false === $old_version ) { - $need_upgrade_sync = true; - } else { - $last_reindex_version = $reindex_versions[ count( $reindex_versions ) - 1 ]; + if ( ( -1 === version_compare( $old_version, $last_reindex_version ) && 1 === version_compare( EP_VERSION , $last_reindex_version ) ) || 0 === version_compare( EP_VERSION , $last_reindex_version ) ) { + $last_reindex_version = true; + } + } - if ( ( -1 === version_compare( $old_version, $last_reindex_version ) && 1 === version_compare( EP_VERSION , $last_reindex_version ) ) || 0 === version_compare( EP_VERSION , $last_reindex_version ) ) { - $last_reindex_version = true; + if ( $need_upgrade_sync ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_need_upgrade_sync', true ); + } else { + update_option( 'ep_need_upgrade_sync', true ); + } } - } - if ( $need_upgrade_sync ) { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_need_upgrade_sync', true ); + update_site_option( 'ep_version', sanitize_text_field( EP_VERSION ) ); } else { - update_option( 'ep_need_upgrade_sync', true ); + update_option( 'ep_version', sanitize_text_field( EP_VERSION ) ); } - } - - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_feature_settings', $feature_settings ); - delete_site_option( 'ep_index_meta' ); - update_site_option( 'ep_version', sanitize_text_field( EP_VERSION ) ); - } else { - update_option( 'ep_feature_settings', $feature_settings ); - delete_option( 'ep_index_meta' ); - update_option( 'ep_version', sanitize_text_field( EP_VERSION ) ); - } } -register_activation_hook( __FILE__, 'ep_on_activate' ); +add_action( 'plugins_loaded', 'ep_handle_upgrades' ); /** * Load text domain and handle debugging + * + * @since 2.2 */ -function ep_loader() { +function ep_setup_misc() { load_plugin_textdomain( 'elasticpress', false, basename( dirname( __FILE__ ) ) . '/lang' ); // Load any available translations first. if ( is_user_logged_in() && ! defined( 'WP_EP_DEBUG' ) ) { @@ -137,4 +118,4 @@ function ep_loader() { define( 'WP_EP_DEBUG', is_plugin_active( 'debug-bar-elasticpress/debug-bar-elasticpress.php' ) ); } } -add_action( 'plugins_loaded', 'ep_loader' ); +add_action( 'plugins_loaded', 'ep_setup_misc' ); From 18b953829f7075476499652ae2aeaff246067f1e Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 7 Dec 2016 21:42:25 +0000 Subject: [PATCH 061/159] properly name auto activation since it only happens when the plugin is first installed --- classes/class-ep-features.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index 70b194a8a2..b00ed7ab5a 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -27,7 +27,7 @@ class EP_Features { */ public function setup() { add_action( 'plugins_loaded', array( $this, 'setup_features' ) ); - add_action( 'plugins_loaded', array( $this, 'auto_activate_features' ) ); + add_action( 'plugins_loaded', array( $this, 'initial_auto_activate_features' ) ); add_action( 'plugins_loaded', array( $this, 'maybe_activate_deactivate_feature' ) ); } @@ -101,11 +101,11 @@ public function get_requirement_statuses( $force = false ) { } /** - * Auto activate features + * Auto activate features for the first time * * @since 2.2 */ - public function auto_activate_features() { + public function initial_auto_activate_features() { if ( ! is_admin() || defined( 'DOING_AJAX' ) ) { return; } From 9026a05fc35e22560275bf678424b9d6ecb7cbf7 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 9 Dec 2016 12:36:49 -0500 Subject: [PATCH 062/159] Remove unnecessary option check --- classes/class-ep-api.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index eb66e16260..32a4b1433a 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -437,12 +437,6 @@ public function create_network_alias( $indexes ) { * @return array|bool|mixed */ public function put_mapping() { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $es_version = get_site_option( 'ep_es_version', false ); - } else { - $es_version = get_option( 'ep_es_version', false ); - } - $es_version = $this->get_elasticsearch_version(); if ( empty( $es_version ) ) { From 54cb440479f2d07b1051145df5e421976e69aced Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 9 Dec 2016 15:10:15 -0500 Subject: [PATCH 063/159] Use get_elasticsearch_version instead of elasticsearch_can_connect; min and max ES compat versions with warnings; improving formatting --- bin/wp-cli.php | 2 +- classes/class-ep-api.php | 40 +++------------- classes/class-ep-dashboard.php | 33 +++++++++++++- classes/class-ep-sync-manager.php | 2 +- elasticpress.php | 76 ++++++++++++++++++------------- includes/dashboard-page.php | 2 +- includes/header.php | 4 +- 7 files changed, 87 insertions(+), 72 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index e205d4d9db..d2bea421a1 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -829,7 +829,7 @@ private function _connect_check() { if ( empty( $host) ) { WP_CLI::error( __( 'There is no Elasticsearch host set up. Either add one through the dashboard or define one in wp-config.php', 'elasticpress' ) ); - } elseif ( ! ep_elasticsearch_can_connect() ) { + } elseif ( ! ep_get_elasticsearch_version() ) { WP_CLI::error( __( 'Unable to reach Elasticsearch Server! Check that service is running.', 'elasticpress' ) ); } } diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 32a4b1433a..ad46b61ddc 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -133,7 +133,7 @@ public function refresh_index() { * Get Elasticsearch version * * @since 2.1.2 - * @return string + * @return string|bool */ public function get_elasticsearch_version() { @@ -1908,38 +1908,6 @@ protected function get_orderby_array( $orderbys ){ return $orderbys; } - /** - * This function checks if we can connect to Elasticsearch - * - * @since 2.1 - * @return bool - */ - public function elasticsearch_can_connect() { - - $host = ep_get_host(); - - if ( empty( $host ) ) { - return false; - } - - $request_args = array( 'headers' => $this->format_request_headers() ); - - $request = wp_remote_request( $host, apply_filters( 'ep_es_can_connect_args', $request_args ) ); - - if ( ! is_wp_error( $request ) ) { - if ( isset( $request['response']['code'] ) && 200 === $request['response']['code'] ) { - - $response_body = json_decode( wp_remote_retrieve_body( $request ), true ); - - if ( ! empty( $response_body ) && ! empty( $response_body['name']) ) { - return true; - } - } - } - - return false; - } - /** * Return queries for debugging * @@ -2402,13 +2370,17 @@ function ep_get_cluster_status() { } function ep_elasticsearch_can_connect() { - return EP_API::factory()->elasticsearch_can_connect(); + return (bool) EP_API::factory()->get_elasticsearch_version(); } function ep_parse_site_id( $index_name ) { return EP_API::factory()->parse_site_id( $index_name ); } +function ep_get_elasticsearch_version() { + return EP_API::factory()->get_elasticsearch_version(); +} + if( ! function_exists( 'ep_search' ) ) { /** * Backward compatibility for ep_search diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index bf503d38ca..8b5ba21998 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -176,7 +176,24 @@ public function maybe_notice() { } } - if ( empty( $host ) || ! ep_elasticsearch_can_connect() ) { + $es_version = ep_get_elasticsearch_version(); + + /** + * Check Elasticsearch version compat + */ + + if ( ! empty( $es_version ) ) { + // First reduce version to major version i.e. 5.1 not 5.1.1 + $major_es_version = preg_replace( '#^([0-9]+\.[0-9]+).*#', '$1', $es_version ); + + if ( -1 === version_compare( EP_ES_VERSION_MAX, $major_es_version ) ) { + $notice = 'above-es-compat'; + } elseif ( 1 === version_compare( EP_ES_VERSION_MIN, $major_es_version ) ) { + $notice = 'below-es-compat'; + } + } + + if ( empty( $host ) || ! $es_version ) { if ( $on_settings_page ) { if ( ! $never_set_host ) { $notice = 'bad-host'; @@ -218,6 +235,20 @@ public function maybe_notice() {
+
+

+
+ +
+

+
+ -
+

diff --git a/includes/header.php b/includes/header.php index 71bfb286d8..50393ce10f 100644 --- a/includes/header.php +++ b/includes/header.php @@ -22,7 +22,7 @@ - + @@ -30,4 +30,4 @@
-
\ No newline at end of file +
From 19e807f7d93de117f83f6b7f1e0070e28e838118 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 12 Dec 2016 10:51:47 -0500 Subject: [PATCH 064/159] Remove unnecessary code --- classes/class-ep-features.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index b00ed7ab5a..1bd68d0117 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -180,12 +180,6 @@ public function maybe_activate_deactivate_feature( $plugin ) { $new_requirement_statuses = $this->get_requirement_statuses( true ); - if ( false === $old_requirement_statuses ) { - // Really weird situation here. Let's just run the EP activation hook function - ep_on_activate(); - return; - } - foreach ( $new_requirement_statuses as $slug => $code ) { $feature = ep_get_registered_feature( $slug ); From 805ab9248b34f3138d8f88ef98b3067ab37994c6 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Tue, 13 Dec 2016 11:34:40 -0500 Subject: [PATCH 065/159] Refactor maybe activate feature; unit tests for notification and feature activation --- classes/class-ep-dashboard.php | 2 + classes/class-ep-features.php | 179 +++++++++----------- elasticpress.php | 2 +- tests/bootstrap.php | 5 +- tests/test-admin-notifications.php | 115 +++++++++++++ tests/test-feature-activation.php | 256 +++++++++++++++++++++++++++++ 6 files changed, 454 insertions(+), 105 deletions(-) create mode 100644 tests/test-admin-notifications.php create mode 100644 tests/test-feature-activation.php diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 8b5ba21998..a31b10ef37 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -304,6 +304,8 @@ public function maybe_notice() { registered_features as $slug => $feature ) { - $status = $feature->requirements_status(); - $requirement_statuses[ $slug ] = (int) $status->code; - } - - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_feature_requirement_statuses', $requirement_statuses ); - } else { - update_option( 'ep_feature_requirement_statuses', $requirement_statuses ); - } - } - - return $requirement_statuses; - } - - /** - * Auto activate features for the first time - * - * @since 2.2 - */ - public function initial_auto_activate_features() { - if ( ! is_admin() || defined( 'DOING_AJAX' ) ) { - return; - } - - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $feature_settings = get_site_option( 'ep_feature_settings', false ); - } else { - $feature_settings = get_option( 'ep_feature_settings', false ); - } - - /** - * Activate necessary features if this is the first time activating - * the plugin. - */ - if ( false === $feature_settings ) { - $registered_features = $this->registered_features; - - foreach ( $registered_features as $slug => $feature ) { - if ( 0 === $feature->requirements_status()->code ) { - ep_activate_feature( $slug ); - } - } - } - - /** - * Cache requirement statuses so we can detect changes later - */ - $this->get_requirement_statuses( true ); - } - /** * Activate or deactivate a feature * @@ -171,38 +100,68 @@ public function activate_feature( $slug, $active = true ) { * * @since 2.2 */ - public function maybe_activate_deactivate_feature( $plugin ) { + public function handle_feature_activation() { + /** + * Save our current requirement statuses for later + */ + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $old_requirement_statuses = get_site_option( 'ep_feature_requirement_statuses', false ); } else { $old_requirement_statuses = get_option( 'ep_feature_requirement_statuses', false ); } - $new_requirement_statuses = $this->get_requirement_statuses( true ); + $new_requirement_statuses = array(); - foreach ( $new_requirement_statuses as $slug => $code ) { - $feature = ep_get_registered_feature( $slug ); + foreach ( $this->registered_features as $slug => $feature ) { + $status = $feature->requirements_status(); + $new_requirement_statuses[ $slug ] = (int) $status->code; + } - // This is a new feature - if ( ! empty( $old_requirement_statuses[ $slug ] ) ) { - if ( 0 === $code ) { - ep_activate_feature( $slug ); + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_feature_requirement_statuses', $new_requirement_statuses ); + } else { + update_option( 'ep_feature_requirement_statuses', $new_requirement_statuses ); + } - if ( $feature->requires_install_reindex ) { - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); - } else { - update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); - } - } + /** + * If feature settings aren't created, let's create them and finish + */ + + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + $feature_settings = get_site_option( 'ep_feature_settings', false ); + } else { + $feature_settings = get_option( 'ep_feature_settings', false ); + } + + if ( false === $feature_settings ) { + $registered_features = $this->registered_features; + + foreach ( $registered_features as $slug => $feature ) { + if ( 0 === $feature->requirements_status()->code ) { + ep_activate_feature( $slug ); } - } else { - // This feature has a 0 "ok" code when it did not before - if ( $old_requirement_statuses[ $slug ] !== $code && ( 0 === $code || 2 === $code ) ) { - $active = ( 0 === $code ); + } + + /** + * Nothing else to do since we are doing initial activation + */ + return; + } + + /** + * If a requirement status changes, we need to handle that by activating/deactivating/showing notification + */ + + if ( ! empty( $old_requirement_statuses ) ) { + foreach ( $new_requirement_statuses as $slug => $code ) { + $feature = ep_get_registered_feature( $slug ); + + // This is a new feature + if ( ! isset( $old_requirement_statuses[ $slug ] ) ) { + if ( 0 === $code ) { + ep_activate_feature( $slug ); - if ( ! $feature->is_active() && $active ) { - // Need to activate and maybe set a sync notice if ( $feature->requires_install_reindex ) { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { update_site_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); @@ -210,12 +169,27 @@ public function maybe_activate_deactivate_feature( $plugin ) { update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); } } - - ep_activate_feature( $slug, $active ); - } elseif ( $feature->is_active() && ! $active ) { - // Just deactivate - - ep_activate_feature( $slug, $active ); + } + } else { + // This feature has a 0 "ok" code when it did not before + if ( $old_requirement_statuses[ $slug ] !== $code && ( 0 === $code || 2 === $code ) ) { + $active = ( 0 === $code ); + + if ( ! $feature->is_active() && $active ) { + // Need to activate and maybe set a sync notice + if ( $feature->requires_install_reindex ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + update_site_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); + } else { + update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); + } + } + + ep_activate_feature( $slug, $active ); + } elseif ( $feature->is_active() && ! $active ) { + // Just deactivate + ep_activate_feature( $slug, $active ); + } } } } @@ -271,10 +245,11 @@ function ep_register_feature( $slug, $feature_args ) { * Activate a feature * * @param string $slug + * @param bool $active * @since 2.2 */ -function ep_activate_feature( $slug ) { - EP_Features::factory()->activate_feature( $slug ); +function ep_activate_feature( $slug, $active = true ) { + EP_Features::factory()->activate_feature( $slug, $active ); } /** diff --git a/elasticpress.php b/elasticpress.php index cde1cef30c..d0d0f6d65c 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -115,7 +115,7 @@ function ep_handle_upgrades() { update_option( 'ep_version', sanitize_text_field( EP_VERSION ) ); } } -add_action( 'plugins_loaded', 'ep_handle_upgrades' ); +add_action( 'plugins_loaded', 'ep_handle_upgrades', 5 ); /** * Load text domain and handle debugging diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5be828e334..3625018ba9 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -23,7 +23,8 @@ function _manually_load_plugin() { $host = 'http://localhost:9200'; } - define( 'EP_HOST', $host ); + update_option( 'ep_host', $host ); + update_site_option( 'ep_host', $host ); require( dirname( __FILE__ ) . '/../vendor/woocommerce/woocommerce.php' ); require( dirname( __FILE__ ) . '/../elasticpress.php' ); @@ -33,7 +34,7 @@ function _manually_load_plugin() { $tries = 5; $sleep = 3; do { - $response = wp_remote_get( EP_HOST ); + $response = wp_remote_get( $host ); if ( 200 == wp_remote_retrieve_response_code( $response ) ) { // Looks good! break; diff --git a/tests/test-admin-notifications.php b/tests/test-admin-notifications.php new file mode 100644 index 0000000000..16f68db8ed --- /dev/null +++ b/tests/test-admin-notifications.php @@ -0,0 +1,115 @@ +suppress_errors(); + + $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + + wp_set_current_user( $admin_id ); + + ep_delete_index(); + ep_put_mapping(); + + EP_WP_Query_Integration::factory()->setup(); + EP_Sync_Manager::factory()->setup(); + EP_Sync_Manager::factory()->sync_post_queue = array(); + + $this->setup_test_post_type(); + + $this->current_host = get_option( 'ep_host' ); + } + + /** + * Clean up after each test. Reset our mocks + * + * @since 2.2 + */ + public function tearDown() { + parent::tearDown(); + + //make sure no one attached to this + remove_filter( 'ep_sync_terms_allow_hierarchy', array( $this, 'ep_allow_multiple_level_terms_sync' ), 100 ); + $this->fired_actions = array(); + + // Update since we are deleting to test notifications + update_option( 'ep_host', $this->current_host ); + } + + /** + * Conditions: + * + * - On edit screem + * - No host set + * - Index page not shown and notice not hidden + * - No sync has occured + * - No upgrade sync is needed + * - Not feature auto activate sync is needed + * + * Do: Show setup notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testInitialSetupNotification() { + set_current_screen( 'edit.php' ); + + delete_option( 'ep_host' ); + delete_option( 'ep_intro_shown' ); + delete_option( 'ep_hide_intro_shown_notice' ); + delete_option( 'ep_last_sync' ); + delete_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_auto_activated_sync' ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertEquals( 'need-setup', $notice ); + } + + /** + * Conditions: + * + * - On edit screem + * - Host set + * - Index page not shown and notice not hidden + * - No sync has occured + * - No upgrade sync is needed + * - Not feature auto activate sync is needed + * + * Do: Show need first sync notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testFirstSyncNotification() { + set_current_screen( 'edit.php' ); + + delete_option( 'ep_intro_shown' ); + delete_option( 'ep_hide_intro_shown_notice' ); + delete_option( 'ep_last_sync' ); + delete_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_auto_activated_sync' ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertEquals( 'no-sync', $notice ); + } +} diff --git a/tests/test-feature-activation.php b/tests/test-feature-activation.php new file mode 100644 index 0000000000..1ab79bce16 --- /dev/null +++ b/tests/test-feature-activation.php @@ -0,0 +1,256 @@ +suppress_errors(); + + $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + + wp_set_current_user( $admin_id ); + + ep_delete_index(); + ep_put_mapping(); + + EP_WP_Query_Integration::factory()->setup(); + EP_Sync_Manager::factory()->setup(); + EP_Sync_Manager::factory()->sync_post_queue = array(); + + $this->setup_test_post_type(); + } + + /** + * Clean up after each test. Reset our mocks + * + * @since 2.2 + */ + public function tearDown() { + parent::tearDown(); + + //make sure no one attached to this + remove_filter( 'ep_sync_terms_allow_hierarchy', array( $this, 'ep_allow_multiple_level_terms_sync' ), 100 ); + $this->fired_actions = array(); + } + + /** + * Make sure no feature settings or req statuses exist at the start. + * + * @group feature-activation + * @since 2.2 + */ + public function testNoActiveFeatures() { + delete_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_settings' ); + + EP_Features::factory()->setup_features(); + + foreach ( EP_Features::factory()->registered_features as $feature ) { + $this->assertEquals( false, $feature->is_active() ); + } + } + + /** + * Test that default EP features are auto-activated + * + * @group feature-activation + * @since 2.2 + */ + public function testAutoActivated() { + delete_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_settings' ); + + EP_Features::factory()->handle_feature_activation(); + EP_Features::factory()->setup_features(); + + $this->assertEquals( true, EP_Features::factory()->registered_features['search']->is_active() ); + $this->assertEquals( 0, EP_Features::factory()->registered_features['search']->requirements_status()->code ); + + $this->assertEquals( false, EP_Features::factory()->registered_features['admin']->is_active() ); + $this->assertEquals( 1, EP_Features::factory()->registered_features['admin']->requirements_status()->code ); + + $this->assertEquals( true, EP_Features::factory()->registered_features['woocommerce']->is_active() ); + $this->assertEquals( 0, EP_Features::factory()->registered_features['woocommerce']->requirements_status()->code ); + + $this->assertEquals( true, EP_Features::factory()->registered_features['related_posts']->is_active() ); + $this->assertEquals( 0, EP_Features::factory()->registered_features['related_posts']->requirements_status()->code ); + } + + /** + * Test that default EP requirement statuses are set properly + * + * @group feature-activation + * @since 2.2 + */ + public function testRequirementStatuses() { + delete_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_settings' ); + + EP_Features::factory()->handle_feature_activation(); + EP_Features::factory()->setup_features(); + + $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + + $this->assertEquals( 0, $requirements_statuses['search'] ); + $this->assertEquals( 1, $requirements_statuses['admin'] ); + $this->assertEquals( 0, $requirements_statuses['related_posts'] ); + $this->assertEquals( 0, $requirements_statuses['woocommerce'] ); + } + + /** + * Test that a simple EP feature is registered and auto-activated properly + * + * @group feature-activation + * @since 2.2 + */ + public function testAutoActivateWithSimpleFeature() { + delete_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_settings' ); + + ep_register_feature( 'test', array( + 'title' => 'Test', + 'requires_install_reindex' => true, + ) ); + + EP_Features::factory()->handle_feature_activation(); + EP_Features::factory()->setup_features(); + + $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); + $this->assertEquals( 0, EP_Features::factory()->registered_features['test']->requirements_status()->code ); + } + + public function simple_feature_requirements_status_cb() { + $on = get_option( 'ep_test_feature_on', 2 ); + + $status = new EP_Feature_Requirements_Status( $on ); + + return $status; + } + + /** + * Test that a simple EP feature is registered and auto-activated properly. Also + * test that when it's req status changes, it's enabled. + * + * @group feature-activation + * @since 2.2 + */ + public function testAutoActivateWithFeature() { + delete_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_settings' ); + + ep_register_feature( 'test', array( + 'title' => 'Test', + 'requires_install_reindex' => true, + 'requirements_status_cb' => array( $this, 'simple_feature_requirements_status_cb' ), + ) ); + + EP_Features::factory()->handle_feature_activation(); + EP_Features::factory()->setup_features(); + + $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + + $this->assertEquals( false, EP_Features::factory()->registered_features['test']->is_active() ); + $this->assertEquals( 2, EP_Features::factory()->registered_features['test']->requirements_status()->code ); + $this->assertEquals( 2, $requirements_statuses['test'] ); + + update_option( 'ep_test_feature_on', 0 ); + + EP_Features::factory()->handle_feature_activation(); + + $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + + $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); + $this->assertEquals( 0, EP_Features::factory()->registered_features['test']->requirements_status()->code ); + $this->assertEquals( 0, $requirements_statuses['test'] ); + } + + /** + * Test that a simple EP feature is registered and auto-activated properly. Also + * test that when it's req status changes, it's disabled. + * + * @group feature-activation + * @since 2.2 + */ + public function testAutoDeactivateWithFeature() { + delete_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_settings' ); + + ep_register_feature( 'test', array( + 'title' => 'Test', + 'requires_install_reindex' => true, + 'requirements_status_cb' => array( $this, 'simple_feature_requirements_status_cb' ), + ) ); + + update_option( 'ep_test_feature_on', 0 ); + + EP_Features::factory()->handle_feature_activation(); + EP_Features::factory()->setup_features(); + + $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + + $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); + $this->assertEquals( 0, EP_Features::factory()->registered_features['test']->requirements_status()->code ); + $this->assertEquals( 0, $requirements_statuses['test'] ); + + update_option( 'ep_test_feature_on', 2 ); + + EP_Features::factory()->handle_feature_activation(); + + $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + + $this->assertEquals( false, EP_Features::factory()->registered_features['test']->is_active() ); + $this->assertEquals( 2, EP_Features::factory()->registered_features['test']->requirements_status()->code ); + $this->assertEquals( 2, $requirements_statuses['test'] ); + } + + /** + * Test that a simple EP feature is registered and auto-activated properly. Test that when the req + * status changes to 1, nothing happens. + * + * @group feature-activation + * @since 2.2 + */ + public function testReqChangeNothingWithFeature() { + delete_option( 'ep_feature_requirement_statuses' ); + delete_option( 'ep_feature_settings' ); + + ep_register_feature( 'test', array( + 'title' => 'Test', + 'requires_install_reindex' => true, + 'requirements_status_cb' => array( $this, 'simple_feature_requirements_status_cb' ), + ) ); + + update_option( 'ep_test_feature_on', 0 ); + + EP_Features::factory()->handle_feature_activation(); + EP_Features::factory()->setup_features(); + + $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + + $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); + $this->assertEquals( 0, EP_Features::factory()->registered_features['test']->requirements_status()->code ); + $this->assertEquals( 0, $requirements_statuses['test'] ); + + update_option( 'ep_test_feature_on', 1 ); + + EP_Features::factory()->handle_feature_activation(); + + $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + + $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); + $this->assertEquals( 1, EP_Features::factory()->registered_features['test']->requirements_status()->code ); + $this->assertEquals( 1, $requirements_statuses['test'] ); + } +} From 08db541ed3ca9f0caa04d6b407c8a906cb1cb8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Wed, 14 Dec 2016 11:33:35 -0300 Subject: [PATCH 066/159] Making stop_the_insanity method compatible with WordPress 4.7 --- bin/wp-cli.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index d2bea421a1..f3cfda18db 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -810,10 +810,21 @@ public function stop_the_insanity() { // } // It's high memory consuming as WP_Query instance holds all query results inside itself // and in theory $wp_filter will not stop growing until Out Of Memory exception occurs. - if ( isset( $wp_filter['get_term_metadata'][10] ) ) { - foreach ( $wp_filter['get_term_metadata'][10] as $hook => $content ) { - if ( preg_match( '#^[0-9a-f]{32}lazyload_term_meta$#', $hook ) ) { - unset( $wp_filter['get_term_metadata'][10][$hook] ); + if ( isset( $wp_filter['get_term_metadata'] ) ) { + /* + * WordPress 4.7 has a new Hook infrastructure, so we need to make sure + * we're accessing the global array properly + */ + if ( $wp_filter['get_term_metadata'] instanceof \WP_Hook ) { + $filter_callbacks = &$wp_filter['get_term_metadata']->callbacks; + } else { + $filter_callbacks = &$wp_filter['get_term_metadata']; + } + if ( isset( $filter_callbacks[10] ) ) { + foreach ( $filter_callbacks[10] as $hook => $content ) { + if ( preg_match( '#^[0-9a-f]{32}lazyload_term_meta$#', $hook ) ) { + unset( $filter_callbacks[10][ $hook ] ); + } } } } From 716a286420721c155a509426dcf53d57f4be93a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Wed, 14 Dec 2016 11:53:15 -0300 Subject: [PATCH 067/159] Checking if WP_Hook exists to avoid fatals if running WP < 4.7 --- bin/wp-cli.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index f3cfda18db..dbeed741d7 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -815,7 +815,7 @@ public function stop_the_insanity() { * WordPress 4.7 has a new Hook infrastructure, so we need to make sure * we're accessing the global array properly */ - if ( $wp_filter['get_term_metadata'] instanceof \WP_Hook ) { + if ( class_exists( 'WP_Hook' ) && $wp_filter['get_term_metadata'] instanceof WP_Hook ) { $filter_callbacks = &$wp_filter['get_term_metadata']->callbacks; } else { $filter_callbacks = &$wp_filter['get_term_metadata']; From b064e93ffba1faa4e1741d8f1a363e89e3f2f7b3 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 14:53:15 -0500 Subject: [PATCH 068/159] Notification tests --- tests/test-admin-notifications.php | 189 ++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 2 deletions(-) diff --git a/tests/test-admin-notifications.php b/tests/test-admin-notifications.php index 16f68db8ed..2912e2a33d 100644 --- a/tests/test-admin-notifications.php +++ b/tests/test-admin-notifications.php @@ -53,7 +53,7 @@ public function tearDown() { /** * Conditions: * - * - On edit screem + * - On edit screan * - No host set * - Index page not shown and notice not hidden * - No sync has occured @@ -85,7 +85,7 @@ public function testInitialSetupNotification() { /** * Conditions: * - * - On edit screem + * - On edit screan * - Host set * - Index page not shown and notice not hidden * - No sync has occured @@ -112,4 +112,189 @@ public function testFirstSyncNotification() { $this->assertEquals( 'no-sync', $notice ); } + + /** + * Conditions: + * + * - On edit screan + * - Host set + * - Index page shown + * - Sync has occured + * - No upgrade sync is needed + * - Not feature auto activate sync is needed + * + * Do: Show no notifications + * + * @group admin-notifications + * @since 2.2 + */ + public function testNoNotifications() { + set_current_screen( 'edit.php' ); + + update_option( 'ep_intro_shown', true ); + update_option( 'ep_hide_intro_shown_notice', true ); + update_option( 'ep_last_sync', time() ); + delete_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_auto_activated_sync' ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertFalse( $notice ); + } + + /** + * Conditions: + * + * - On edit screan + * - No host set + * - Index page not shown and notice not hidden + * - No sync has occured + * - Upgrade sync is needed + * - Feature auto activate sync is needed + * + * Do: Show setup notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testNotificationPrioritySetup() { + set_current_screen( 'edit.php' ); + + delete_option( 'ep_host' ); + delete_option( 'ep_intro_shown' ); + delete_option( 'ep_hide_intro_shown_notice' ); + delete_option( 'ep_last_sync' ); + update_option( 'ep_need_upgrade_sync', true ); + update_option( 'ep_feature_auto_activated_sync', true ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertEquals( 'need-setup', $notice ); + } + + /** + * Conditions: + * + * - On edit screan + * - Host set + * - Index page shown and notice not hidden + * - No sync has occured + * - Upgrade sync is needed + * - Feature auto activate sync is needed + * + * Do: Show no sync notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testNotificationPrioritySync() { + set_current_screen( 'edit.php' ); + update_option( 'ep_intro_shown', true ); + delete_option( 'ep_hide_intro_shown_notice' ); + delete_option( 'ep_last_sync' ); + update_option( 'ep_need_upgrade_sync', true ); + update_option( 'ep_feature_auto_activated_sync', true ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertEquals( 'no-sync', $notice ); + } + + /** + * Conditions: + * + * - On edit screan + * - Bad host set + * - Index page shown and notice not hidden + * - No sync has occured + * - No upgrade sync is needed + * - No feature auto activate sync is needed + * + * Do: Show bad host notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testBadHostNotification() { + set_current_screen( 'edit.php' ); + + update_option( 'ep_host', 'bad' ); + update_option( 'ep_intro_shown', true ); + delete_option( 'ep_hide_intro_shown_notice' ); + delete_option( 'ep_last_sync' ); + delete_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_auto_activated_sync' ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertEquals( 'bad-host', $notice ); + } + + /** + * Conditions: + * + * - On edit screan + * - Host set + * - Index page shown and notice not hidden + * - Sync has occured + * - Upgrade sync is needed + * - No feature auto activate sync is needed + * + * Do: Show upgrade sync notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testUpgradeSyncNotification() { + set_current_screen( 'edit.php' ); + update_option( 'ep_intro_shown', true ); + delete_option( 'ep_hide_intro_shown_notice' ); + update_option( 'ep_last_sync', time() ); + update_option( 'ep_need_upgrade_sync', true ); + delete_option( 'ep_feature_auto_activated_sync' ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertEquals( 'upgrade-sync', $notice ); + } + + /** + * Conditions: + * + * - On edit screan + * - Host set + * - Index page shown and notice not hidden + * - Sync has occured + * - No upgrade sync is needed + * - Feature auto activate sync is needed + * + * Do: Show upgrade sync notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testFeatureSyncNotification() { + set_current_screen( 'edit.php' ); + update_option( 'ep_intro_shown', true ); + delete_option( 'ep_hide_intro_shown_notice' ); + update_option( 'ep_last_sync', time() ); + delete_option( 'ep_need_upgrade_sync' ); + update_option( 'ep_feature_auto_activated_sync', 'woocommerce' ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + $this->assertEquals( 'auto-activate-sync', $notice ); + } } From 73293549ebc5d61896915e19d2f66c4b57f5fbc3 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 15:22:39 -0500 Subject: [PATCH 069/159] Upgrade notification testing; properly get ES version --- classes/class-ep-api.php | 12 ++-- classes/class-ep-dashboard.php | 4 +- tests/test-admin-notifications.php | 98 ++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index ad46b61ddc..5e1fa6897a 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -141,6 +141,8 @@ public function get_elasticsearch_version() { $request = ep_remote_request( '', apply_filters( 'ep_elasticsearch_version_request_args', $request_args ) ); + $version = false; + if ( ! is_wp_error( $request ) ) { if ( isset( $request['response']['code'] ) && 200 === $request['response']['code'] ) { $response_body = wp_remote_retrieve_body( $request ); @@ -148,16 +150,16 @@ public function get_elasticsearch_version() { $response = json_decode( $response_body, true ); try { - $version = $response['version']['number']; + if ( ! empty( $response['version']['number'] ) ) { + $version = $response['version']['number']; + } } catch ( Exception $e ) { - return false; + $version = false; } - - return $version; } } - return false; + return apply_filters( 'ep_elasticsearch_version', $version ); } /** diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index a31b10ef37..f1bb05f8e4 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -182,7 +182,7 @@ public function maybe_notice() { * Check Elasticsearch version compat */ - if ( ! empty( $es_version ) ) { + if ( false !== $es_version ) { // First reduce version to major version i.e. 5.1 not 5.1.1 $major_es_version = preg_replace( '#^([0-9]+\.[0-9]+).*#', '$1', $es_version ); @@ -193,7 +193,7 @@ public function maybe_notice() { } } - if ( empty( $host ) || ! $es_version ) { + if ( empty( $host ) || false === $es_version ) { if ( $on_settings_page ) { if ( ! $never_set_host ) { $notice = 'bad-host'; diff --git a/tests/test-admin-notifications.php b/tests/test-admin-notifications.php index 2912e2a33d..12fcc9fac5 100644 --- a/tests/test-admin-notifications.php +++ b/tests/test-admin-notifications.php @@ -59,6 +59,7 @@ public function tearDown() { * - No sync has occured * - No upgrade sync is needed * - Not feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show setup notification * @@ -91,6 +92,7 @@ public function testInitialSetupNotification() { * - No sync has occured * - No upgrade sync is needed * - Not feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show need first sync notification * @@ -122,6 +124,7 @@ public function testFirstSyncNotification() { * - Sync has occured * - No upgrade sync is needed * - Not feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show no notifications * @@ -153,6 +156,7 @@ public function testNoNotifications() { * - No sync has occured * - Upgrade sync is needed * - Feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show setup notification * @@ -185,6 +189,7 @@ public function testNotificationPrioritySetup() { * - No sync has occured * - Upgrade sync is needed * - Feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show no sync notification * @@ -215,6 +220,7 @@ public function testNotificationPrioritySync() { * - No sync has occured * - No upgrade sync is needed * - No feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show bad host notification * @@ -247,6 +253,7 @@ public function testBadHostNotification() { * - Sync has occured * - Upgrade sync is needed * - No feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show upgrade sync notification * @@ -277,6 +284,7 @@ public function testUpgradeSyncNotification() { * - Sync has occured * - No upgrade sync is needed * - Feature auto activate sync is needed + * - Elasticsearch version within bounds * * Do: Show upgrade sync notification * @@ -297,4 +305,94 @@ public function testFeatureSyncNotification() { $this->assertEquals( 'auto-activate-sync', $notice ); } + + /** + * Conditions: + * + * - On edit screan + * - Host set + * - Index page shown and notice not hidden + * - Sync has occured + * - No upgrade sync is needed + * - No feature auto activate sync is needed + * - Elasticsearch version above bounds + * + * Do: Show upgrade sync notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testAboveESCompatNotification() { + set_current_screen( 'edit.php' ); + update_option( 'ep_intro_shown', true ); + delete_option( 'ep_hide_intro_shown_notice' ); + update_option( 'ep_last_sync', time() ); + delete_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_auto_activated_sync' ); + + add_filter( 'ep_elasticsearch_version', array( $this, '_filter_es_version_above' ) ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + remove_filter( 'ep_elasticsearch_version', array( $this, '_filter_es_version_above' ) ); + + $this->assertEquals( 'above-es-compat', $notice ); + } + + /** + * Conditions: + * + * - On edit screan + * - Host set + * - Index page shown and notice not hidden + * - Sync has occured + * - No upgrade sync is needed + * - No feature auto activate sync is needed + * - Elasticsearch version above bounds + * + * Do: Show upgrade sync notification + * + * @group admin-notifications + * @since 2.2 + */ + public function testBelowESCompatNotification() { + set_current_screen( 'edit.php' ); + update_option( 'ep_intro_shown', true ); + delete_option( 'ep_hide_intro_shown_notice' ); + update_option( 'ep_last_sync', time() ); + delete_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_auto_activated_sync' ); + + add_filter( 'ep_elasticsearch_version', array( $this, '_filter_es_version_below' ) ); + + ob_start(); + $notice = EP_Dashboard::factory()->maybe_notice(); + ob_get_clean(); + + remove_filter( 'ep_elasticsearch_version', array( $this, '_filter_es_version_below' ) ); + + $this->assertEquals( 'below-es-compat', $notice ); + } + + /** + * Filter in ES version that is too high + * + * @since 2.2 + * @return int + */ + public function _filter_es_version_above() { + return ( (int) EP_ES_VERSION_MAX ) + 1; + } + + /** + * Filter in ES version that is too low + * + * @since 2.2 + * @return int + */ + public function _filter_es_version_below() { + return ( (int) EP_ES_VERSION_MIN ) - 1; + } } From c54e9dd4d055e9d7b18a302be2f4ca88e724f488 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 15:33:33 -0500 Subject: [PATCH 070/159] Formatting --- classes/class-ep-wp-query-integration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/class-ep-wp-query-integration.php b/classes/class-ep-wp-query-integration.php index de610100e6..caa720c599 100644 --- a/classes/class-ep-wp-query-integration.php +++ b/classes/class-ep-wp-query-integration.php @@ -328,4 +328,4 @@ public static function factory() { } } -EP_WP_Query_Integration::factory(); \ No newline at end of file +EP_WP_Query_Integration::factory(); From 377c563813fa37a09da6ce0ab131a2bb87748ae5 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 15:33:58 -0500 Subject: [PATCH 071/159] Cant test search off since setup fires before test --- tests/features/test-search.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tests/features/test-search.php b/tests/features/test-search.php index b44cab7983..8d05774fe7 100644 --- a/tests/features/test-search.php +++ b/tests/features/test-search.php @@ -39,33 +39,6 @@ public function tearDown() { $this->fired_actions = array(); } - /** - * Test that search is off by default - * - * @since 2.1 - * @group search - */ - public function testSearchOff() { - EP_Features::factory()->setup_features(); - $post_ids = array(); - - ep_create_and_sync_post(); - ep_create_and_sync_post(); - ep_create_and_sync_post( array( 'post_content' => 'findme' ) ); - - ep_refresh_index(); - - add_action( 'ep_wp_query_search', array( $this, 'action_wp_query_search' ), 10, 0 ); - - $args = array( - 's' => 'findme', - ); - - $query = new WP_Query( $args ); - - $this->assertTrue( empty( $this->fired_actions['ep_wp_query_search'] ) ); - } - /** * Test that search is on * From 42ff8f63ceb34baf18ed553efb44aca1b8065b79 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 15:34:55 -0500 Subject: [PATCH 072/159] Cant test WC off since setup fires before test --- tests/features/test-woocommerce.php | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/tests/features/test-woocommerce.php b/tests/features/test-woocommerce.php index 4ee68ae82f..1e91146068 100644 --- a/tests/features/test-woocommerce.php +++ b/tests/features/test-woocommerce.php @@ -43,31 +43,6 @@ public function tearDown() { $this->fired_actions = array(); } - /** - * Test products post type query doesn't get integrated when the feature is not active - * - * @since 2.1 - * @group Wwoocommerce - */ - public function testProductsPostTypeQueryOff() { - EP_Features::factory()->setup_features(); - - ep_create_and_sync_post(); - ep_create_and_sync_post( array( 'post_content' => 'product 1', 'post_type' => 'product' ) ); - - ep_refresh_index(); - - add_action( 'ep_wp_query_search', array( $this, 'action_wp_query_search' ), 10, 0 ); - - $args = array( - 'post_type' => 'product', - ); - - $query = new WP_Query( $args ); - - $this->assertTrue( empty( $this->fired_actions['ep_wp_query_search'] ) ); - } - /** * Test products post type query does get integrated when the feature is not active * From ad90c14fb333d192e00de9f0601789d69dac6ca3 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 16:43:43 -0500 Subject: [PATCH 073/159] Make tests happen in network admin; remove unpublish test since WC feature has been activated already --- tests/bootstrap.php | 3 + tests/test-admin-notifications.php | 148 ++++++++++++++--------------- tests/test-feature-activation.php | 54 +++++------ tests/test-multisite.php | 41 +------- 4 files changed, 100 insertions(+), 146 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3625018ba9..bdf4c6f927 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -26,6 +26,9 @@ function _manually_load_plugin() { update_option( 'ep_host', $host ); update_site_option( 'ep_host', $host ); + define( 'EP_IS_NETWORK', true ); + define( 'WP_NETWORK_ADMIN', true ); + require( dirname( __FILE__ ) . '/../vendor/woocommerce/woocommerce.php' ); require( dirname( __FILE__ ) . '/../elasticpress.php' ); diff --git a/tests/test-admin-notifications.php b/tests/test-admin-notifications.php index 12fcc9fac5..9550713d60 100644 --- a/tests/test-admin-notifications.php +++ b/tests/test-admin-notifications.php @@ -19,6 +19,7 @@ public function setUp() { $wpdb->suppress_errors(); $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + grant_super_admin( $admin_id ); wp_set_current_user( $admin_id ); @@ -32,6 +33,10 @@ public function setUp() { $this->setup_test_post_type(); $this->current_host = get_option( 'ep_host' ); + + global $hook_suffix; + $hook_suffix = 'sites.php'; + set_current_screen(); } /** @@ -47,13 +52,13 @@ public function tearDown() { $this->fired_actions = array(); // Update since we are deleting to test notifications - update_option( 'ep_host', $this->current_host ); + update_site_option( 'ep_host', $this->current_host ); } /** * Conditions: * - * - On edit screan + * - On sites secreen * - No host set * - Index page not shown and notice not hidden * - No sync has occured @@ -67,14 +72,12 @@ public function tearDown() { * @since 2.2 */ public function testInitialSetupNotification() { - set_current_screen( 'edit.php' ); - - delete_option( 'ep_host' ); - delete_option( 'ep_intro_shown' ); - delete_option( 'ep_hide_intro_shown_notice' ); - delete_option( 'ep_last_sync' ); - delete_option( 'ep_need_upgrade_sync' ); - delete_option( 'ep_feature_auto_activated_sync' ); + delete_site_option( 'ep_host' ); + delete_site_option( 'ep_intro_shown' ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + delete_site_option( 'ep_last_sync' ); + delete_site_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -86,7 +89,7 @@ public function testInitialSetupNotification() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - Host set * - Index page not shown and notice not hidden * - No sync has occured @@ -100,13 +103,11 @@ public function testInitialSetupNotification() { * @since 2.2 */ public function testFirstSyncNotification() { - set_current_screen( 'edit.php' ); - - delete_option( 'ep_intro_shown' ); - delete_option( 'ep_hide_intro_shown_notice' ); - delete_option( 'ep_last_sync' ); - delete_option( 'ep_need_upgrade_sync' ); - delete_option( 'ep_feature_auto_activated_sync' ); + delete_site_option( 'ep_intro_shown' ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + delete_site_option( 'ep_last_sync' ); + delete_site_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -118,7 +119,7 @@ public function testFirstSyncNotification() { /** * Conditions: * - * - On edit screan + * - On sites screan * - Host set * - Index page shown * - Sync has occured @@ -132,13 +133,11 @@ public function testFirstSyncNotification() { * @since 2.2 */ public function testNoNotifications() { - set_current_screen( 'edit.php' ); - - update_option( 'ep_intro_shown', true ); - update_option( 'ep_hide_intro_shown_notice', true ); - update_option( 'ep_last_sync', time() ); - delete_option( 'ep_need_upgrade_sync' ); - delete_option( 'ep_feature_auto_activated_sync' ); + update_site_option( 'ep_intro_shown', true ); + update_site_option( 'ep_hide_intro_shown_notice', true ); + update_site_option( 'ep_last_sync', time() ); + delete_site_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -150,7 +149,7 @@ public function testNoNotifications() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - No host set * - Index page not shown and notice not hidden * - No sync has occured @@ -164,14 +163,12 @@ public function testNoNotifications() { * @since 2.2 */ public function testNotificationPrioritySetup() { - set_current_screen( 'edit.php' ); - - delete_option( 'ep_host' ); - delete_option( 'ep_intro_shown' ); - delete_option( 'ep_hide_intro_shown_notice' ); - delete_option( 'ep_last_sync' ); - update_option( 'ep_need_upgrade_sync', true ); - update_option( 'ep_feature_auto_activated_sync', true ); + delete_site_option( 'ep_host' ); + delete_site_option( 'ep_intro_shown' ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + delete_site_option( 'ep_last_sync' ); + update_site_option( 'ep_need_upgrade_sync', true ); + update_site_option( 'ep_feature_auto_activated_sync', true ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -183,7 +180,7 @@ public function testNotificationPrioritySetup() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - Host set * - Index page shown and notice not hidden * - No sync has occured @@ -197,12 +194,11 @@ public function testNotificationPrioritySetup() { * @since 2.2 */ public function testNotificationPrioritySync() { - set_current_screen( 'edit.php' ); - update_option( 'ep_intro_shown', true ); - delete_option( 'ep_hide_intro_shown_notice' ); - delete_option( 'ep_last_sync' ); - update_option( 'ep_need_upgrade_sync', true ); - update_option( 'ep_feature_auto_activated_sync', true ); + update_site_option( 'ep_intro_shown', true ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + delete_site_option( 'ep_last_sync' ); + update_site_option( 'ep_need_upgrade_sync', true ); + update_site_option( 'ep_feature_auto_activated_sync', true ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -214,7 +210,7 @@ public function testNotificationPrioritySync() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - Bad host set * - Index page shown and notice not hidden * - No sync has occured @@ -228,14 +224,12 @@ public function testNotificationPrioritySync() { * @since 2.2 */ public function testBadHostNotification() { - set_current_screen( 'edit.php' ); - - update_option( 'ep_host', 'bad' ); - update_option( 'ep_intro_shown', true ); - delete_option( 'ep_hide_intro_shown_notice' ); - delete_option( 'ep_last_sync' ); - delete_option( 'ep_need_upgrade_sync' ); - delete_option( 'ep_feature_auto_activated_sync' ); + update_site_option( 'ep_host', 'bad' ); + update_site_option( 'ep_intro_shown', true ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + delete_site_option( 'ep_last_sync' ); + delete_site_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -247,7 +241,7 @@ public function testBadHostNotification() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - Host set * - Index page shown and notice not hidden * - Sync has occured @@ -261,12 +255,11 @@ public function testBadHostNotification() { * @since 2.2 */ public function testUpgradeSyncNotification() { - set_current_screen( 'edit.php' ); - update_option( 'ep_intro_shown', true ); - delete_option( 'ep_hide_intro_shown_notice' ); - update_option( 'ep_last_sync', time() ); - update_option( 'ep_need_upgrade_sync', true ); - delete_option( 'ep_feature_auto_activated_sync' ); + update_site_option( 'ep_intro_shown', true ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + update_site_option( 'ep_last_sync', time() ); + update_site_option( 'ep_need_upgrade_sync', true ); + delete_site_option( 'ep_feature_auto_activated_sync' ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -278,7 +271,7 @@ public function testUpgradeSyncNotification() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - Host set * - Index page shown and notice not hidden * - Sync has occured @@ -292,12 +285,11 @@ public function testUpgradeSyncNotification() { * @since 2.2 */ public function testFeatureSyncNotification() { - set_current_screen( 'edit.php' ); - update_option( 'ep_intro_shown', true ); - delete_option( 'ep_hide_intro_shown_notice' ); - update_option( 'ep_last_sync', time() ); - delete_option( 'ep_need_upgrade_sync' ); - update_option( 'ep_feature_auto_activated_sync', 'woocommerce' ); + update_site_option( 'ep_intro_shown', true ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + update_site_option( 'ep_last_sync', time() ); + delete_site_option( 'ep_need_upgrade_sync' ); + update_site_option( 'ep_feature_auto_activated_sync', 'woocommerce' ); ob_start(); $notice = EP_Dashboard::factory()->maybe_notice(); @@ -309,7 +301,7 @@ public function testFeatureSyncNotification() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - Host set * - Index page shown and notice not hidden * - Sync has occured @@ -323,12 +315,11 @@ public function testFeatureSyncNotification() { * @since 2.2 */ public function testAboveESCompatNotification() { - set_current_screen( 'edit.php' ); - update_option( 'ep_intro_shown', true ); - delete_option( 'ep_hide_intro_shown_notice' ); - update_option( 'ep_last_sync', time() ); - delete_option( 'ep_need_upgrade_sync' ); - delete_option( 'ep_feature_auto_activated_sync' ); + update_site_option( 'ep_intro_shown', true ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + update_site_option( 'ep_last_sync', time() ); + delete_site_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); add_filter( 'ep_elasticsearch_version', array( $this, '_filter_es_version_above' ) ); @@ -344,7 +335,7 @@ public function testAboveESCompatNotification() { /** * Conditions: * - * - On edit screan + * - On sites secreen * - Host set * - Index page shown and notice not hidden * - Sync has occured @@ -358,12 +349,11 @@ public function testAboveESCompatNotification() { * @since 2.2 */ public function testBelowESCompatNotification() { - set_current_screen( 'edit.php' ); - update_option( 'ep_intro_shown', true ); - delete_option( 'ep_hide_intro_shown_notice' ); - update_option( 'ep_last_sync', time() ); - delete_option( 'ep_need_upgrade_sync' ); - delete_option( 'ep_feature_auto_activated_sync' ); + update_site_option( 'ep_intro_shown', true ); + delete_site_option( 'ep_hide_intro_shown_notice' ); + update_site_option( 'ep_last_sync', time() ); + delete_site_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); add_filter( 'ep_elasticsearch_version', array( $this, '_filter_es_version_below' ) ); diff --git a/tests/test-feature-activation.php b/tests/test-feature-activation.php index 1ab79bce16..06829697d1 100644 --- a/tests/test-feature-activation.php +++ b/tests/test-feature-activation.php @@ -52,8 +52,8 @@ public function tearDown() { * @since 2.2 */ public function testNoActiveFeatures() { - delete_option( 'ep_feature_requirement_statuses' ); - delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_settings' ); EP_Features::factory()->setup_features(); @@ -69,8 +69,8 @@ public function testNoActiveFeatures() { * @since 2.2 */ public function testAutoActivated() { - delete_option( 'ep_feature_requirement_statuses' ); - delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_settings' ); EP_Features::factory()->handle_feature_activation(); EP_Features::factory()->setup_features(); @@ -95,13 +95,13 @@ public function testAutoActivated() { * @since 2.2 */ public function testRequirementStatuses() { - delete_option( 'ep_feature_requirement_statuses' ); - delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_settings' ); EP_Features::factory()->handle_feature_activation(); EP_Features::factory()->setup_features(); - $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + $requirements_statuses = get_site_option( 'ep_feature_requirement_statuses' ); $this->assertEquals( 0, $requirements_statuses['search'] ); $this->assertEquals( 1, $requirements_statuses['admin'] ); @@ -116,8 +116,8 @@ public function testRequirementStatuses() { * @since 2.2 */ public function testAutoActivateWithSimpleFeature() { - delete_option( 'ep_feature_requirement_statuses' ); - delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_settings' ); ep_register_feature( 'test', array( 'title' => 'Test', @@ -132,7 +132,7 @@ public function testAutoActivateWithSimpleFeature() { } public function simple_feature_requirements_status_cb() { - $on = get_option( 'ep_test_feature_on', 2 ); + $on = get_site_option( 'ep_test_feature_on', 2 ); $status = new EP_Feature_Requirements_Status( $on ); @@ -147,8 +147,8 @@ public function simple_feature_requirements_status_cb() { * @since 2.2 */ public function testAutoActivateWithFeature() { - delete_option( 'ep_feature_requirement_statuses' ); - delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_settings' ); ep_register_feature( 'test', array( 'title' => 'Test', @@ -159,17 +159,17 @@ public function testAutoActivateWithFeature() { EP_Features::factory()->handle_feature_activation(); EP_Features::factory()->setup_features(); - $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + $requirements_statuses = get_site_option( 'ep_feature_requirement_statuses' ); $this->assertEquals( false, EP_Features::factory()->registered_features['test']->is_active() ); $this->assertEquals( 2, EP_Features::factory()->registered_features['test']->requirements_status()->code ); $this->assertEquals( 2, $requirements_statuses['test'] ); - update_option( 'ep_test_feature_on', 0 ); + update_site_option( 'ep_test_feature_on', 0 ); EP_Features::factory()->handle_feature_activation(); - $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + $requirements_statuses = get_site_option( 'ep_feature_requirement_statuses' ); $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); $this->assertEquals( 0, EP_Features::factory()->registered_features['test']->requirements_status()->code ); @@ -184,8 +184,8 @@ public function testAutoActivateWithFeature() { * @since 2.2 */ public function testAutoDeactivateWithFeature() { - delete_option( 'ep_feature_requirement_statuses' ); - delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_settings' ); ep_register_feature( 'test', array( 'title' => 'Test', @@ -193,22 +193,22 @@ public function testAutoDeactivateWithFeature() { 'requirements_status_cb' => array( $this, 'simple_feature_requirements_status_cb' ), ) ); - update_option( 'ep_test_feature_on', 0 ); + update_site_option( 'ep_test_feature_on', 0 ); EP_Features::factory()->handle_feature_activation(); EP_Features::factory()->setup_features(); - $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + $requirements_statuses = get_site_option( 'ep_feature_requirement_statuses' ); $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); $this->assertEquals( 0, EP_Features::factory()->registered_features['test']->requirements_status()->code ); $this->assertEquals( 0, $requirements_statuses['test'] ); - update_option( 'ep_test_feature_on', 2 ); + update_site_option( 'ep_test_feature_on', 2 ); EP_Features::factory()->handle_feature_activation(); - $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + $requirements_statuses = get_site_option( 'ep_feature_requirement_statuses' ); $this->assertEquals( false, EP_Features::factory()->registered_features['test']->is_active() ); $this->assertEquals( 2, EP_Features::factory()->registered_features['test']->requirements_status()->code ); @@ -223,8 +223,8 @@ public function testAutoDeactivateWithFeature() { * @since 2.2 */ public function testReqChangeNothingWithFeature() { - delete_option( 'ep_feature_requirement_statuses' ); - delete_option( 'ep_feature_settings' ); + delete_site_option( 'ep_feature_requirement_statuses' ); + delete_site_option( 'ep_feature_settings' ); ep_register_feature( 'test', array( 'title' => 'Test', @@ -232,22 +232,22 @@ public function testReqChangeNothingWithFeature() { 'requirements_status_cb' => array( $this, 'simple_feature_requirements_status_cb' ), ) ); - update_option( 'ep_test_feature_on', 0 ); + update_site_option( 'ep_test_feature_on', 0 ); EP_Features::factory()->handle_feature_activation(); EP_Features::factory()->setup_features(); - $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + $requirements_statuses = get_site_option( 'ep_feature_requirement_statuses' ); $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); $this->assertEquals( 0, EP_Features::factory()->registered_features['test']->requirements_status()->code ); $this->assertEquals( 0, $requirements_statuses['test'] ); - update_option( 'ep_test_feature_on', 1 ); + update_site_option( 'ep_test_feature_on', 1 ); EP_Features::factory()->handle_feature_activation(); - $requirements_statuses = get_option( 'ep_feature_requirement_statuses' ); + $requirements_statuses = get_site_option( 'ep_feature_requirement_statuses' ); $this->assertEquals( true, EP_Features::factory()->registered_features['test']->is_active() ); $this->assertEquals( 1, EP_Features::factory()->registered_features['test']->requirements_status()->code ); diff --git a/tests/test-multisite.php b/tests/test-multisite.php index 7c2d3a7328..3473f5839c 100644 --- a/tests/test-multisite.php +++ b/tests/test-multisite.php @@ -13,6 +13,7 @@ public function setUp() { $wpdb->suppress_errors(); $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + grant_super_admin( $admin_id ); $this->factory->blog->create_many( 2, array( 'user_id' => $admin_id ) ); @@ -98,46 +99,6 @@ public function testPostSync() { } } - /** - * Test that a post becoming unpublished correctly gets removed from the Elasticsearch index - * - * @since 0.9.3 - */ - public function testPostUnpublish() { - $sites = ep_get_sites(); - - foreach( $sites as $site ) { - switch_to_blog( $site['blog_id'] ); - - add_action( 'ep_delete_post', array( $this, 'action_delete_post' ), 10, 0 ); - - $post_id = ep_create_and_sync_post(); - - ep_refresh_index(); - - $post = ep_get_post( $post_id ); - - // Ensure that our post made it over to elasticsearch - $this->assertTrue( ! empty( $post ) ); - - // Let's transition the post status from published to draft - wp_update_post( array( 'ID' => $post_id, 'post_status' => 'draft' ) ); - - ep_refresh_index(); - - $this->assertTrue( ! empty( $this->fired_actions['ep_delete_post'] ) ); - - $post = ep_get_post( $post_id ); - - // Alright, now the post has been removed from the index, so this should be empty - $this->assertTrue( empty( $post ) ); - - $this->fired_actions = array(); - - restore_current_blog(); - } - } - /** * Test a simple post content search * From f142ecd499991ba2cf066ee69c58227a5c58e4d7 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 17:43:24 -0500 Subject: [PATCH 074/159] Make sure WC doesnt set post type if a WC taxonomy exists #642 --- features/woocommerce/woocommerce.php | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/features/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php index 620cdd59b1..5814c22e73 100644 --- a/features/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -269,25 +269,11 @@ function ep_wc_translate_args( $query ) { } } - $post_type = $query->get( 'post_type', false ); - - if ( ! empty( $tax_query ) ) { - $query->set( 'tax_query', $tax_query ); - - if ( empty( $post_type ) ) { - $post_type = 'product'; - } elseif ( is_array( $post_type ) ) { - $post_type[] = 'product'; - } else { - $post_type = array( $post_type, 'product' ); - } - - $query->set( 'post_type', $post_type ); - } - /** * Force ElasticPress if product post type query */ + $post_type = $query->get( 'post_type', false ); + $supported_post_types = array( 'product', 'shop_order', @@ -304,6 +290,14 @@ function ep_wc_translate_args( $query ) { * If we have a WooCommerce specific query, lets hook it to ElasticPress and make the query ElasticSearch friendly */ if ( $integrate ) { + // Set tax_query again since we may have added things + $query->set( 'tax_query', 'tax_query' ); + + // Default to product if no post type is set + if ( empty( $post_type ) ) { + $post_type = 'product'; + $query->set( 'post_type', 'product' ); + } // Handles the WC Top Rated Widget if ( has_filter( 'posts_clauses', array( WC()->query, 'order_by_rating_post_clauses' ) ) ) { From 54aaeba7e5137a94033a0945fcbc95ba0102f3d9 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 14 Dec 2016 20:18:35 -0500 Subject: [PATCH 075/159] Properly set tax_query #642 --- features/woocommerce/woocommerce.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php index 5814c22e73..0917e898b1 100644 --- a/features/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -291,7 +291,7 @@ function ep_wc_translate_args( $query ) { */ if ( $integrate ) { // Set tax_query again since we may have added things - $query->set( 'tax_query', 'tax_query' ); + $query->set( 'tax_query', $tax_query ); // Default to product if no post type is set if ( empty( $post_type ) ) { From 143dbdf8416ae231660198dffadc7ed386709ee8 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 15 Dec 2016 10:47:37 -0500 Subject: [PATCH 076/159] Prevent error when query object is not WP_Query --- features/woocommerce/woocommerce.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php index 0917e898b1..6f6f2cc99b 100644 --- a/features/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -471,7 +471,7 @@ function ep_wc_remove_legacy_meta( $post_args, $post_id ) { * @return bool */ function ep_wc_blacklist_coupons( $enabled, $query ) { - if ( 'shop_coupon' === $query->get( 'post_type' ) ) { + if ( method_exists( $query, 'get' ) && 'shop_coupon' === $query->get( 'post_type' ) ) { return false; } From 0983f7940d87dab392318562e824193b91760078 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 15 Dec 2016 15:56:13 -0500 Subject: [PATCH 077/159] Properly init roles for 4.7+ --- tests/bootstrap.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index bdf4c6f927..0dcaf77c7c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -62,7 +62,8 @@ function _setup_theme() { include( dirname( __FILE__ ) . '/../vendor/woocommerce/uninstall.php' ); WC_Install::install(); - $GLOBALS['wp_roles']->reinit(); + $GLOBALS['wp_roles'] = new WP_Roles(); + echo "Installing WooCommerce..." . PHP_EOL; } tests_add_filter( 'setup_theme', '_setup_theme' ); From 6c2602c621bc5a674b254d3382e8c13fbc298122 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 15 Dec 2016 15:59:33 -0500 Subject: [PATCH 078/159] Dont sync post if it doesnt exist --- classes/class-ep-sync-manager.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/classes/class-ep-sync-manager.php b/classes/class-ep-sync-manager.php index 31254cd932..504e7283aa 100644 --- a/classes/class-ep-sync-manager.php +++ b/classes/class-ep-sync-manager.php @@ -249,10 +249,16 @@ public static function factory() { */ public function sync_post( $post_id, $blocking = true ) { + $post = get_post( $post_id ); + + if ( empty( $post ) ) { + return false; + } + $post_args = ep_prepare_post( $post_id ); if ( apply_filters( 'ep_post_sync_kill', false, $post_args, $post_id ) ) { - return; + return false; } $response = ep_index_post( $post_args, $blocking ); From 7756eca7459649225e98ace697574155e67cae04 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 15 Dec 2016 16:58:34 -0500 Subject: [PATCH 079/159] Add meta key to EP returned post object --- classes/class-ep-wp-query-integration.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/classes/class-ep-wp-query-integration.php b/classes/class-ep-wp-query-integration.php index caa720c599..15fac680f9 100644 --- a/classes/class-ep-wp-query-integration.php +++ b/classes/class-ep-wp-query-integration.php @@ -278,9 +278,10 @@ public function filter_posts_request( $request, $query ) { 'menu_order', 'permalink', 'terms', - 'post_meta' - ) - ); + 'post_meta', + 'meta', + ) + ); foreach ( $post_return_args as $key ) { if( $key === 'post_author' ) { From a24e975f68de573f7f5ce6aa23feaba83e2e2a19 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 15 Dec 2016 17:10:16 -0500 Subject: [PATCH 080/159] Remove tests that dont make sense --- tests/test-single-site.php | 134 +------------------------------------ 1 file changed, 1 insertion(+), 133 deletions(-) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index 73925cea1c..a747d6e0af 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -140,38 +140,6 @@ public function testPaginationWithOffset() { $this->assertEquals( 'two', $query->posts[0]->post_title ); } - /** - * Test that a post becoming unpublished correctly gets removed from the Elasticsearch index - * - * @since 0.9.3 - */ - public function testPostUnpublish() { - add_action( 'ep_delete_post', array( $this, 'action_delete_post' ), 10, 0 ); - - $post_id = ep_create_and_sync_post(); - - ep_refresh_index(); - - $post = ep_get_post( $post_id ); - - // Ensure that our post made it over to elasticsearch - $this->assertTrue( ! empty( $post ) ); - - // Let's transition the post status from published to draft - wp_update_post( array( 'ID' => $post_id, 'post_status' => 'draft' ) ); - - ep_refresh_index(); - - $this->assertTrue( ! empty( $this->fired_actions['ep_delete_post'] ) ); - - $post = ep_get_post( $post_id ); - - // Alright, now the post has been removed from the index, so this should be empty - $this->assertTrue( empty( $post ) ); - - $this->fired_actions = array(); - } - /** * Test WP Query search on post content * @@ -1051,32 +1019,6 @@ public function testPostStatusQueryDraft() { remove_filter( 'ep_indexable_post_status', array( $this, 'mock_indexable_post_status' ), 10); } - /** - * Test a post status query for published or draft posts without 'draft' allowed as indexable status - * - * @since 2.1 - */ - public function testPostStatusQueryMultiDefault() { - ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_status' => 'draft' ) ); - ep_create_and_sync_post( array( 'post_content' => 'findme test 2' ) ); - ep_create_and_sync_post( array( 'post_content' => 'findme test 3', 'post_status' => 'draft' ) ); - - ep_refresh_index(); - - $args = array( - 's' => 'findme', - 'post_status' => array( - 'draft', - 'publish', - ), - ); - - $query = new WP_Query( $args ); - - $this->assertEquals( 1, $query->post_count ); - $this->assertEquals( 1, $query->found_posts ); - } - /** * Test a post status query for published or draft posts with 'draft' whitelisted as indexable status * @@ -1107,56 +1049,6 @@ public function testPostStatusQueryMulti() { remove_filter( 'ep_indexable_post_status', array( $this, 'mock_indexable_post_status' ), 10); } - /** - * Test a query with no post status without 'draft' indexable status. Post status should default to publish. - * - * @since 2.1 - */ - public function testNoPostStatusSearchQueryDefault() { - ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_status' => 'draft' ) ); - ep_create_and_sync_post( array( 'post_content' => 'findme test 2' ) ); - ep_create_and_sync_post( array( 'post_content' => 'findme test 3', 'post_status' => 'draft' ) ); - - ep_refresh_index(); - - // post_status defaults to "publish" - $args = array( - 's' => 'findme', - ); - - $query = new WP_Query( $args ); - - $this->assertEquals( 1, $query->post_count ); - $this->assertEquals( 1, $query->found_posts ); - } - - /** - * Test a query with no post status with 'draft' as indexable status. Post status should default to publish - * - * @since 2.1 - */ - public function testNoPostStatusSearchQuery() { - add_filter( 'ep_indexable_post_status', array( $this, 'mock_indexable_post_status' ), 10, 1 ); - - ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_status' => 'draft' ) ); - ep_create_and_sync_post( array( 'post_content' => 'findme test 2' ) ); - ep_create_and_sync_post( array( 'post_content' => 'findme test 3', 'post_status' => 'draft' ) ); - - ep_refresh_index(); - - // post_status defaults to "publish" - $args = array( - 's' => 'findme', - ); - - $query = new WP_Query( $args ); - - $this->assertEquals( 1, $query->post_count ); - $this->assertEquals( 1, $query->found_posts ); - - remove_filter( 'ep_indexable_post_status', array( $this, 'mock_indexable_post_status' ), 10); - } - /** * Add attachment post type for indexing * @@ -1300,31 +1192,7 @@ public function testSearchMetaMappingComplexObject() { $this->assertEquals( 1, $query->post_count ); - $this->assertEquals( 1, count( $query->posts[0]->post_meta['test_key'] ) ); // Make sure there is only one value - - $this->assertEquals( 'hello', unserialize( $query->posts[0]->post_meta['test_key'][0] )->test ); // Make sure value is properly serialized - } - - /** - * Test meta mapping for simple string - * - * @since 1.7 - */ - public function testSearchMetaMappingString() { - ep_create_and_sync_post( array( 'post_content' => 'post content' ), array( 'test_key' => 'test' ) ); - - ep_refresh_index(); - $args = array( - 'ep_integrate' => true, - ); - - $query = new WP_Query( $args ); - - $this->assertEquals( 1, $query->post_count ); - - $this->assertEquals( 1, count( $query->posts[0]->post_meta['test_key'] ) ); // Make sure there is only one value - - $this->assertEquals( 'test', $query->posts[0]->post_meta['test_key'][0] ); + $this->assertEquals( 1, count( $query->posts[0]->meta['test_key'] ) ); // Make sure there is only one value } /** From fda58fc757dbb6ac9c4736e6fb5aab9dfdce3ee8 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 15 Dec 2016 17:15:39 -0500 Subject: [PATCH 081/159] Remove unnecessary meta in post object tests --- tests/test-single-site.php | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index a747d6e0af..eec1f59f7d 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -1151,33 +1151,11 @@ public function testAnyPostTypeQuery() { } /** - * Test meta mapping for complex arrays. All complex arrays are serialized + * Test meta shows up in EP post object * * @since 1.7 */ - public function testSearchMetaMappingComplexArray() { - ep_create_and_sync_post( array( 'post_content' => 'post content' ), array( 'test_key' => array( 'test' ) ) ); - - ep_refresh_index(); - $args = array( - 'ep_integrate' => true, - ); - - $query = new WP_Query( $args ); - - $this->assertEquals( 1, $query->post_count ); - - $this->assertEquals( 1, count( $query->posts[0]->post_meta['test_key'] ) ); // Make sure there is only one value - - $this->assertTrue( is_array( unserialize( $query->posts[0]->post_meta['test_key'][0] ) ) ); // Make sure value is properly serialized - } - - /** - * Test meta mapping for complex objects. All complex objects are serialized - * - * @since 1.7 - */ - public function testSearchMetaMappingComplexObject() { + public function testSearchMetaInPostObject() { $object = new stdClass(); $object->test = 'hello'; @@ -1192,7 +1170,7 @@ public function testSearchMetaMappingComplexObject() { $this->assertEquals( 1, $query->post_count ); - $this->assertEquals( 1, count( $query->posts[0]->meta['test_key'] ) ); // Make sure there is only one value + $this->assertEquals( 1, count( $query->posts[0]->meta['test_key'] ) ); } /** From 5bfd9d4160966dcc663f27ec3515a2a5a77be24e Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 16 Dec 2016 12:15:09 -0500 Subject: [PATCH 082/159] WP query doesnt let us sort by an array of multiple unwhitelisted key so we need to change the test --- tests/test-single-site.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index eec1f59f7d..3011a87be3 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -1414,15 +1414,14 @@ public function testSearchPostMetaStringOrderbyQueryAscArray() { */ public function testSearchPostMetaStringOrderbyQueryAdvanced() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ), array( 'test_key' => 'c', 'test_key2' => 'c' ) ); - ep_create_and_sync_post( array( 'post_title' => 'Ordertest 222' ), array( 'test_key' => 'd', 'test_key2' => 'c' ) ); + ep_create_and_sync_post( array( 'post_title' => 'ordertest 222' ), array( 'test_key' => 'f', 'test_key2' => 'c' ) ); ep_create_and_sync_post( array( 'post_title' => 'ordertest 111' ), array( 'test_key' => 'd', 'test_key2' => 'd' ) ); ep_refresh_index(); $args = array( 's' => 'ordertest', - 'orderby' => array( 'meta.test_key.value.sortable' => 'asc', 'meta.test_key.value.sortable' => 'desc' ), - 'order' => 'ASC', + 'orderby' => array( 'meta.test_key.value.sortable' => 'asc', ), ); $query = new WP_Query( $args ); @@ -1430,7 +1429,7 @@ public function testSearchPostMetaStringOrderbyQueryAdvanced() { $this->assertEquals( 3, $query->post_count ); $this->assertEquals( 3, $query->found_posts ); $this->assertEquals( 'ordertest 333', $query->posts[0]->post_title ); - $this->assertEquals( 'Ordertest 111', $query->posts[1]->post_title ); + $this->assertEquals( 'ordertest 111', $query->posts[1]->post_title ); $this->assertEquals( 'ordertest 222', $query->posts[2]->post_title ); } From 15162367b7f6d6f90b992361fe0acefb42424a74 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 16 Dec 2016 12:19:34 -0500 Subject: [PATCH 083/159] Cant test normal delete because of admin feature loading --- tests/test-single-site.php | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index 3011a87be3..569d96a273 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -1859,40 +1859,6 @@ public function testRandOrderby() { $this->assertEquals( 3, $query->found_posts ); } - /** - * Test a normal post trash - * - * @since 1.2 - */ - public function testPostDelete() { - add_action( 'ep_delete_post', array( $this, 'action_delete_post' ), 10, 0 ); - $post_id = ep_create_and_sync_post(); - - ep_refresh_index(); - - $post = ep_get_post( $post_id ); - - // Ensure that our post made it over to elasticsearch - $this->assertTrue( ! empty( $post ) ); - - // Let's normally trash the post - wp_delete_post( $post_id ); - - ep_refresh_index(); - - $this->assertTrue( ! empty( $this->fired_actions['ep_delete_post'] ) ); - - $post = ep_get_post( $post_id ); - - // The post, although it still should exist in WP's trash, should not be in our index - $this->assertTrue( empty( $post ) ); - - $post = get_post( $post_id ); - $this->assertTrue( ! empty( $post ) ); - - $this->fired_actions = array(); - } - /** * Test that a post being directly deleted gets correctly removed from the Elasticsearch index * From 3eab72a1234e1e667301cdef0e81f1d6baa59136 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 16 Dec 2016 13:15:33 -0500 Subject: [PATCH 084/159] Only use searchable post types for search --- classes/class-ep-wp-query-integration.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/classes/class-ep-wp-query-integration.php b/classes/class-ep-wp-query-integration.php index 15fac680f9..2fcb372d22 100644 --- a/classes/class-ep-wp-query-integration.php +++ b/classes/class-ep-wp-query-integration.php @@ -198,6 +198,8 @@ public function filter_found_posts_query( $sql, $query ) { * @return string */ public function filter_posts_request( $request, $query ) { + global $wpdb; + if ( ! ep_elasticpress_enabled( $query ) || apply_filters( 'ep_skip_query_integration', false, $query ) ) { return $request; } @@ -215,6 +217,23 @@ public function filter_posts_request( $request, $query ) { unset( $query_vars['post_type'] ); } + /** + * If not search and not set default to post. If not set and is search, use searchable post tpyes + */ + if ( empty( $query_vars['post_type'] ) ) { + if ( empty( $query_vars['s'] ) ) { + $query_vars['post_type'] = 'post'; + } else { + $query_vars['post_type'] = get_post_types( array( 'exclude_from_search' => false ) ); + } + } + + if ( empty( $query_vars['post_type'] ) ) { + $this->posts_by_query[spl_object_hash( $query )] = array(); + + return "SELECT * FROM $wpdb->posts WHERE 1=0"; + } + $new_posts = apply_filters( 'ep_wp_query_search_cached_posts', array(), $query ); $ep_query = array(); @@ -305,8 +324,6 @@ public function filter_posts_request( $request, $query ) { do_action( 'ep_wp_query_search', $new_posts, $ep_query, $query ); - global $wpdb; - return "SELECT * FROM $wpdb->posts WHERE 1=0"; } From 8c0c68d83f5fcfe2ee13ce5fb1abdb15fcd1df07 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 00:57:16 -0500 Subject: [PATCH 085/159] Continue tests on failure --- phpunit.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 9953fff498..952a70b33e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -5,7 +5,6 @@ convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" - stopOnFailure="true" > From 775d3df6c474445a6bf1fc7d079ec89110e987a5 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 01:05:52 -0500 Subject: [PATCH 086/159] Fix setup features test; add test groups --- tests/test-multisite.php | 82 +++++++++++++++++++----- tests/test-single-site.php | 128 ++++++++++++++++++++++++++++++------- 2 files changed, 169 insertions(+), 41 deletions(-) diff --git a/tests/test-multisite.php b/tests/test-multisite.php index 3473f5839c..62e4d71cc2 100644 --- a/tests/test-multisite.php +++ b/tests/test-multisite.php @@ -75,6 +75,7 @@ public function tearDown() { * Test a simple post sync * * @since 0.9 + * @group multisite */ public function testPostSync() { $sites = ep_get_sites(); @@ -103,6 +104,7 @@ public function testPostSync() { * Test a simple post content search * * @since 0.9 + * @group multisite */ public function testWPQuerySearchContent() { $sites = ep_get_sites(); @@ -168,6 +170,7 @@ public function testWPQuerySearchContent() { * Test a simple post content search on a subset of network sites * * @since 0.9.2 + * @group multisite */ public function testWPQuerySearchContentSiteSubset() { $sites = ep_get_sites(); @@ -199,6 +202,7 @@ public function testWPQuerySearchContentSiteSubset() { * Test to ensure that if we pass an invalid blog_id to the 'sites' parameter that it doesn't break the search * * @since 0.9.2 + * @group multisite */ public function testInvalidSubsites() { $sites = ep_get_sites(); @@ -231,6 +235,7 @@ public function testInvalidSubsites() { * Test a simple post content search on a single site on the network * * @since 0.9.2 + * @group multisite */ public function testWPQuerySearchContentSingleSite() { $sites = ep_get_sites(); @@ -262,6 +267,7 @@ public function testWPQuerySearchContentSingleSite() { * Test that post data is setup correctly after switch_to_blog() * * @since 0.9.2 + * @group multisite */ public function testWPQueryPostDataSetup() { $sites = ep_get_sites(); @@ -311,6 +317,7 @@ public function testWPQueryPostDataSetup() { * Test a simple post title search * * @since 0.9 + * @group multisite */ public function testWPQuerySearchTitle() { $sites = ep_get_sites(); @@ -345,6 +352,7 @@ public function testWPQuerySearchTitle() { * Test a simple post excerpt search * * @since 0.9 + * @group multisite */ public function testWPQuerySearchExcerpt() { $sites = ep_get_sites(); @@ -385,6 +393,7 @@ public function testWPQuerySearchExcerpt() { /** * Test a simple date param search by date and monthnum * + * @group multisite */ public function testSimpleDateMonthNum() { ep_create_date_query_posts(); @@ -415,6 +424,7 @@ public function testSimpleDateMonthNum() { /** * Test a simple date param search by day number of week * + * @group multisite */ public function testSimpleDateDay() { ep_create_date_query_posts(); @@ -434,6 +444,7 @@ public function testSimpleDateDay() { /** * Test a date query with before and after range * + * @group multisite */ public function testDateQueryBeforeAfter() { ep_create_date_query_posts(); @@ -466,6 +477,7 @@ public function testDateQueryBeforeAfter() { /** * Test a date query with multiple column range comparison * + * @group multisite */ public function testDateQueryMultiColumn() { ep_create_date_query_posts(); @@ -494,6 +506,8 @@ public function testDateQueryMultiColumn() { /** * Test a date query with multiple column range comparison inclusive + * + * @group multisite */ public function testDateQueryMultiColumnInclusive() { ep_create_date_query_posts(); @@ -523,6 +537,8 @@ public function testDateQueryMultiColumnInclusive() { /** * Test a date query with multiple eltries + * + * @group multisite */ public function testDateQueryWorkingHours() { ep_create_date_query_posts(); @@ -555,6 +571,8 @@ public function testDateQueryWorkingHours() { /** * Test a date query with multiple column range comparison not inclusive + * + * @group multisite */ public function testDateQueryMultiColumnNotInclusive() { ep_create_date_query_posts(); @@ -583,7 +601,8 @@ public function testDateQueryMultiColumnNotInclusive() { /** * Test a simple date query search by year, monthnum and day of week - * + * + * @group multisite */ public function testDateQuerySimple() { ep_create_date_query_posts(); @@ -609,6 +628,7 @@ public function testDateQuerySimple() { /** * Test a date query with BETWEEN comparison * + * @group multisite */ public function testDateQueryBetween() { ep_create_date_query_posts(); @@ -633,6 +653,7 @@ public function testDateQueryBetween() { /** * Test a date query with NOT BETWEEN comparison * + * @group multisite */ public function testDateQueryNotBetween() { ep_create_date_query_posts(); @@ -657,6 +678,7 @@ public function testDateQueryNotBetween() { /** * Test a date query with BETWEEN comparison on 1 day range * + * @group multisite */ public function testDateQueryShortBetween() { ep_create_date_query_posts(); @@ -685,6 +707,7 @@ public function testDateQueryShortBetween() { * Currently created posts don't have that many date based differences * for this test * + * @group multisite */ public function testDateQueryCompare() { ep_create_date_query_posts(); @@ -717,6 +740,8 @@ public function testDateQueryCompare() { /** * Test a date query with multiple range comparisons where before and after are * structured differently. Test inclusive range. + * + * @group multisite */ public function testDateQueryInclusiveTypeMix() { ep_create_date_query_posts(); @@ -749,6 +774,8 @@ public function testDateQueryInclusiveTypeMix() { /** * Test a date query with multiple range comparisons where before and after are * structured differently. Test exclusive range. + * + * @group multisite */ public function testDateQueryExclusiveTypeMix() { ep_create_date_query_posts(); @@ -777,6 +804,8 @@ public function testDateQueryExclusiveTypeMix() { /** * Test another date query with multiple range comparisons + * + * @group multisite */ public function testDateQueryCompare2() { ep_create_date_query_posts(); @@ -808,6 +837,8 @@ public function testDateQueryCompare2() { /** * Test date query where posts are only pulled from weekdays + * + * @group multisite */ public function testDateQueryWeekdayRange() { ep_create_date_query_posts(); @@ -833,6 +864,7 @@ public function testDateQueryWeekdayRange() { * Test a tax query search * * @since 1.0 + * @group multisite */ public function testTaxQuery() { $sites = ep_get_sites(); @@ -877,6 +909,7 @@ public function testTaxQuery() { * Test a post type query search for pages * * @since 1.3 + * @group multisite */ public function testPostTypeSearchQueryPage() { $sites = ep_get_sites(); @@ -915,6 +948,7 @@ public function testPostTypeSearchQueryPage() { * Test a post type query search for posts * * @since 1.3 + * @group multisite */ public function testPostTypeSearchQueryPost() { $sites = ep_get_sites(); @@ -953,6 +987,7 @@ public function testPostTypeSearchQueryPost() { * Test a post type query search where no post type is specified * * @since 1.3 + * @group multisite */ public function testNoPostTypeSearchQuery() { $sites = ep_get_sites(); @@ -990,6 +1025,7 @@ public function testNoPostTypeSearchQuery() { * Test a post type query non-search where no post type is specified. Defaults to `post` post type * * @since 1.3 + * @group multisite */ public function testNoPostTypeNoSearchQuery() { $sites = ep_get_sites(); @@ -1027,6 +1063,7 @@ public function testNoPostTypeNoSearchQuery() { * Test an author ID query * * @since 1.0 + * @group multisite */ public function testAuthorIDQuery() { $sites = ep_get_sites(); @@ -1067,6 +1104,7 @@ public function testAuthorIDQuery() { * Test an author name query * * @since 1.0 + * @group multisite */ public function testAuthorNameQuery() { $sites = ep_get_sites(); @@ -1107,6 +1145,7 @@ public function testAuthorNameQuery() { * Test a fuzzy search on meta * * @since 1.0 + * @group multisite */ public function testSearchMetaQuery() { $sites = ep_get_sites(); @@ -1150,6 +1189,7 @@ public function testSearchMetaQuery() { * Test a search with a filter on meta * * @since 1.3 + * @group multisite */ public function testFilterMetaQuery() { $sites = ep_get_sites(); @@ -1202,6 +1242,7 @@ public function testFilterMetaQuery() { * Test a fuzzy search on taxonomy terms * * @since 1.0 + * @group multisite */ public function testSearchTaxQuery() { $sites = ep_get_sites(); @@ -1245,6 +1286,7 @@ public function testSearchTaxQuery() { * Test a fuzzy search on author names * * @since 1.0 + * @group multisite */ public function testSearchAuthorQuery() { $sites = ep_get_sites(); @@ -1290,6 +1332,7 @@ public function testSearchAuthorQuery() { * Test a fuzzy search on taxonomy terms * * @since 1.0 + * @group multisite */ public function testAdvancedQuery() { $user_id = $this->factory->user->create( array( 'user_login' => 'john', 'role' => 'administrator' ) ); @@ -1362,6 +1405,7 @@ public function testAdvancedQuery() { * Test pagination * * @since 0.9 + * @group multisite */ public function testPagination() { $sites = ep_get_sites(); @@ -1415,6 +1459,7 @@ public function testPagination() { * Test query restoration after wp_reset_postdata * * @since 0.9.2 + * @group multisite */ public function testQueryRestorationResetPostData() { $old_blog_id = get_current_blog_id(); @@ -1469,6 +1514,7 @@ public function testQueryRestorationResetPostData() { * Test query restoration after wp_reset_query * * @since 0.9.2 + * @group multisite */ public function testQueryRestorationResetQuery() { $old_blog_id = get_current_blog_id(); @@ -1526,6 +1572,7 @@ public function testQueryRestorationResetQuery() { * Test query stack with nested queries * * @since 1.2 + * @group multisite */ public function testQueryStack() { $old_blog_id = get_current_blog_id(); @@ -1597,6 +1644,7 @@ public function testQueryStack() { * Test filter for skipping query integration * * @since 1.2 + * @group multisite */ public function testQueryIntegrationSkip() { $main_post_id = $this->factory->post->create(); @@ -1643,6 +1691,7 @@ public function testQueryIntegrationSkip() { * Test post object data * * @since 1.4 + * @group multisite */ public function testPostObject() { $sites = ep_get_sites(); @@ -1685,6 +1734,8 @@ public function testPostObject() { /** * Test index_exists helper function + * + * @group multisite */ public function testIndexExists() { $sites = ep_get_sites(); @@ -1699,6 +1750,8 @@ public function testIndexExists() { /** * Tests Deletion of index when a blog is deleted + * + * @group multisite */ public function testDeleteIndex( ) { $index_count = ep_count_indexes(); @@ -1716,8 +1769,9 @@ public function testDeleteIndex( ) { /** * Tests deletion of index when a blog is deleted - * @group 392 + * * @link https://github.com/10up/ElasticPress/issues/392 + * @group multisite */ public function testDeactivateSite( ) { $index_count = ep_count_indexes(); @@ -1736,7 +1790,8 @@ public function testDeactivateSite( ) { /** * Tests deletion of index when a blog is marked as spam - * @group 392 + * + * @group multisite * @link https://github.com/10up/ElasticPress/issues/392 */ public function testSpamSite( ) { @@ -1755,7 +1810,8 @@ public function testSpamSite( ) { /** * Tests deletion of index when a blog is marked as archived - * @group 392 + * + * @group multisite * @link https://github.com/10up/ElasticPress/issues/392 */ public function testArchivedSite( ) { @@ -1785,7 +1841,8 @@ public function testQueryWithoutIsSearch() { /** * Check if elasticpress_enabled() properly handles an object with the is_search() method. - * @group 285 + * + * @group multisite * @link https://github.com/10up/ElasticPress/issues/285 */ public function testQueryWithIsSearch() { @@ -1804,10 +1861,7 @@ public function testQueryWithIsSearch() { * Tests index status when site is and is not indexed. * * @since 0.1.0 - * - * @group BBPE-251 - * - * @return void + * @group multisite */ function testGetIndexStatus() { @@ -1832,10 +1886,7 @@ function testGetIndexStatus() { * Test search status. * * @since 0.1.0 - * - * @group BBPE-251 - * - * @return void + * @group multisite */ function testGetSearchStatus() { @@ -1860,10 +1911,7 @@ function testGetSearchStatus() { * Test cluster status. * * @since 0.1.0 - * - * @group BBPE-251 - * - * @return void + * @group multisite */ function testGetClusterStatus() { diff --git a/tests/test-single-site.php b/tests/test-single-site.php index 569d96a273..9646b0546a 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -54,6 +54,7 @@ public function tearDown() { * Test a simple post sync * * @since 0.9 + * @group single-site */ public function testPostSync() { add_action( 'ep_sync_on_transition', array( $this, 'action_sync_on_transition' ), 10, 0 ); @@ -72,6 +73,7 @@ public function testPostSync() { * Test a post sync on meta add * * @since 2.0 + * @group single-site */ public function testPostSyncOnMetaAdd() { add_action( 'ep_sync_on_meta_update', array( $this, 'action_sync_on_meta_update' ), 10, 0 ); @@ -96,6 +98,7 @@ public function testPostSyncOnMetaAdd() { * Test a post sync on meta update * * @since 2.0 + * @group single-site */ public function testPostSyncOnMetaUpdate() { add_action( 'ep_sync_on_meta_update', array( $this, 'action_sync_on_meta_update' ), 10, 0 ); @@ -122,6 +125,7 @@ public function testPostSyncOnMetaUpdate() { * Test pagination with offset * * @since 2.1 + * @group single-site */ public function testPaginationWithOffset() { ep_create_and_sync_post( array( 'post_title' => 'one' ) ); @@ -144,6 +148,7 @@ public function testPaginationWithOffset() { * Test WP Query search on post content * * @since 0.9 + * @group single-site */ public function testWPQuerySearchContent() { $post_ids = array(); @@ -195,6 +200,7 @@ public function testWPQuerySearchContent() { * Test WP Query search on post title * * @since 0.9 + * @group single-site */ public function testWPQuerySearchTitle() { $post_ids = array(); @@ -225,6 +231,7 @@ public function testWPQuerySearchTitle() { * Make sure proper taxonomies are synced with post. Hidden taxonomies should be skipped! * * @since 0.1.1 + * @group single-site */ public function testPostTermSync() { @@ -245,8 +252,7 @@ public function testPostTermSync() { } /** - * @group testPostTermSyncHierarchy - * + * @group single-site */ public function testPostTermSyncSingleLevel(){ @@ -290,8 +296,7 @@ public function ep_allow_multiple_level_terms_sync(){ } /** - * @group testPostTermSyncHierarchy - * + * @group single-site */ public function testPostTermSyncHierarchyMultipleLevel(){ @@ -332,8 +337,7 @@ public function testPostTermSyncHierarchyMultipleLevel(){ } /** - * @group testPostTermSyncHierarchy - * + * @group single-site */ public function testPostTermSyncHierarchyMultipleLevelQuery(){ @@ -380,8 +384,7 @@ public function testPostTermSyncHierarchyMultipleLevelQuery(){ } /** - * @group testPostTermSyncHierarchy - * + * @group single-site */ public function testPostTermSyncSingleLevelQuery(){ @@ -428,8 +431,7 @@ public function testPostTermSyncSingleLevelQuery(){ } /** - * @group testPostImplicitTaxonomyQuery - * + * @group single-site */ public function testPostImplicitTaxonomyQueryCustomTax(){ @@ -461,8 +463,7 @@ public function testPostImplicitTaxonomyQueryCustomTax(){ /** - * @group testPostImplicitTaxonomyQuery - * + * @group single-site */ public function testPostImplicitTaxonomyQueryCategoryName(){ @@ -489,8 +490,7 @@ public function testPostImplicitTaxonomyQueryCategoryName(){ } /** - * @group testPostImplicitTaxonomyQuery - * + * @group single-site */ public function testPostImplicitTaxonomyQueryTag(){ @@ -520,6 +520,7 @@ public function testPostImplicitTaxonomyQueryTag(){ * Test WP Query search on post excerpt * * @since 0.9 + * @group single-site */ public function testWPQuerySearchExcerpt() { $post_ids = array(); @@ -550,6 +551,7 @@ public function testWPQuerySearchExcerpt() { * Test pagination * * @since 0.9 + * @group single-site */ public function testPagination() { ep_create_and_sync_post( array( 'post_excerpt' => 'findme test 1' ) ); @@ -624,6 +626,7 @@ public function testPagination() { * Test a taxonomy query with slug field * * @since 1.8 + * @group single-site */ public function testTaxQuerySlug() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'tags_input' => array( 'one', 'two' ) ) ); @@ -653,6 +656,7 @@ public function testTaxQuerySlug() { * Test a taxonomy query with OR relation * * @since 2.0 + * @group single-site */ public function testTaxQueryOrRelation() { $cat1 = wp_create_category( 'category one' ); @@ -691,6 +695,7 @@ public function testTaxQueryOrRelation() { * Test a taxonomy query with term id field * * @since 1.8 + * @group single-site */ public function testTaxQueryTermId() { $post = ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'tags_input' => array( 'one', 'two' ) ) ); @@ -744,6 +749,7 @@ public function testTaxQueryTermId() { * Test a taxonomy query with term name field * * @since 1.8 + * @group single-site */ public function testTaxQueryTermName() { $cat1 = wp_create_category( 'category one' ); @@ -777,6 +783,7 @@ public function testTaxQueryTermName() { * Test a category_name query * * @since 1.5 + * @group single-site */ public function testCategoryNameQuery() { $cat_one = wp_insert_category( array( 'cat_name' => 'one') ); @@ -803,6 +810,7 @@ public function testCategoryNameQuery() { * Test a post__in query * * @since 1.5 + * @group single-site */ public function testPostInQuery() { $post_ids = array(); @@ -828,6 +836,7 @@ public function testPostInQuery() { * Test a post__not_in query * * @since 1.5 + * @group single-site */ public function testPostNotInQuery() { $post_ids = array(); @@ -853,6 +862,7 @@ public function testPostNotInQuery() { * Test an author ID query * * @since 1.0 + * @group single-site */ public function testAuthorIDQuery() { $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); @@ -878,6 +888,7 @@ public function testAuthorIDQuery() { * Test an author name query * * @since 1.0 + * @group single-site */ public function testAuthorNameQuery() { $user_id = $this->factory->user->create( array( 'user_login' => 'john', 'role' => 'administrator' ) ); @@ -903,6 +914,7 @@ public function testAuthorNameQuery() { * Test a post type query for pages * * @since 1.3 + * @group single-site */ public function testPostTypeQueryPage() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_type' => 'page' ) ); @@ -927,6 +939,7 @@ public function testPostTypeQueryPage() { * Test a post type query for posts * * @since 1.3 + * @group single-site */ public function testPostTypeQueryPost() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_type' => 'page' ) ); @@ -950,6 +963,7 @@ public function testPostTypeQueryPost() { * Test a query with no post type * * @since 1.3 + * @group single-site */ public function testNoPostTypeSearchQuery() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_type' => 'page' ) ); @@ -973,6 +987,7 @@ public function testNoPostTypeSearchQuery() { * Test a post status query for published posts * * @since 2.1 + * @group single-site */ public function testPostStatusQueryPublish() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_status' => 'draft' ) ); @@ -996,6 +1011,7 @@ public function testPostStatusQueryPublish() { * Test a post status query for draft posts * * @since 2.1 + * @group single-site */ public function testPostStatusQueryDraft() { add_filter( 'ep_indexable_post_status', array( $this, 'mock_indexable_post_status' ), 10, 1 ); @@ -1023,6 +1039,7 @@ public function testPostStatusQueryDraft() { * Test a post status query for published or draft posts with 'draft' whitelisted as indexable status * * @since 2.1 + * @group single-site */ public function testPostStatusQueryMulti() { add_filter( 'ep_indexable_post_status', array( $this, 'mock_indexable_post_status' ), 10, 1 ); @@ -1077,6 +1094,7 @@ public function _add_attachment_post_status( $post_statuses ) { * Test an attachment query * * @since 1.6 + * @group single-site */ public function testAttachmentQuery() { add_filter( 'ep_indexable_post_types', array( $this, '_add_attachment_post_type' ) ); @@ -1108,6 +1126,7 @@ public function testAttachmentQuery() { * Test a query with no post type on non-search query. Should default to `post` post type * * @since 1.3 + * @group single-site */ public function testNoPostTypeNonSearchQuery() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_type' => 'page' ) ); @@ -1131,6 +1150,7 @@ public function testNoPostTypeNonSearchQuery() { * Test a query with "any" post type * * @since 1.3 + * @group single-site */ public function testAnyPostTypeQuery() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'post_type' => 'page' ) ); @@ -1154,6 +1174,7 @@ public function testAnyPostTypeQuery() { * Test meta shows up in EP post object * * @since 1.7 + * @group single-site */ public function testSearchMetaInPostObject() { $object = new stdClass(); @@ -1177,6 +1198,7 @@ public function testSearchMetaInPostObject() { * Test a query that fuzzy searches meta * * @since 1.0 + * @group single-site */ public function testSearchMetaQuery() { ep_create_and_sync_post( array( 'post_content' => 'the post content' ) ); @@ -1217,6 +1239,7 @@ public function testSearchMetaQuery() { * Test a query that fuzzy searches taxonomy terms * * @since 1.0 + * @group single-site */ public function testSearchTaxQuery() { ep_create_and_sync_post( array( 'post_content' => 'the post content' ) ); @@ -1244,6 +1267,7 @@ public function testSearchTaxQuery() { * Test a fuzzy author name query * * @since 1.0 + * @group single-site */ public function testSearchAuthorQuery() { $user_id = $this->factory->user->create( array( 'user_login' => 'john', 'role' => 'administrator' ) ); @@ -1274,6 +1298,7 @@ public function testSearchAuthorQuery() { * Test a crazy advanced query * * @since 1.0 + * @group single-site */ public function testAdvancedQuery() { $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); @@ -1329,6 +1354,7 @@ public function testAdvancedQuery() { * Test post_title orderby query * * @since 1.1 + * @group single-site */ public function testSearchPostTitleOrderbyQuery() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ) ); @@ -1356,6 +1382,7 @@ public function testSearchPostTitleOrderbyQuery() { * Test post meta string orderby query asc * * @since 1.8 + * @group single-site */ public function testSearchPostMetaStringOrderbyQueryAsc() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ), array( 'test_key' => 'c' ) ); @@ -1383,6 +1410,7 @@ public function testSearchPostMetaStringOrderbyQueryAsc() { * Test post meta string orderby query asc array * * @since 2.1 + * @group single-site */ public function testSearchPostMetaStringOrderbyQueryAscArray() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ), array( 'test_key' => 'c' ) ); @@ -1411,6 +1439,7 @@ public function testSearchPostMetaStringOrderbyQueryAscArray() { * like array( 'key' => 'order direction ' ) * * @since 2.1 + * @group single-site */ public function testSearchPostMetaStringOrderbyQueryAdvanced() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ), array( 'test_key' => 'c', 'test_key2' => 'c' ) ); @@ -1437,6 +1466,7 @@ public function testSearchPostMetaStringOrderbyQueryAdvanced() { * Sort by an author login * * @since 1.8 + * @group single-site */ public function testAuthorLoginOrderbyQueryAsc() { $bob = $this->factory->user->create( array( 'user_login' => 'Bob', 'role' => 'administrator' ) ); @@ -1469,6 +1499,7 @@ public function testAuthorLoginOrderbyQueryAsc() { * Sort by an author display name * * @since 1.8 + * @group single-site */ public function testAuthorDisplayNameOrderbyQueryAsc() { $bob = $this->factory->user->create( array( 'display_name' => 'Bob', 'role' => 'administrator' ) ); @@ -1501,6 +1532,7 @@ public function testAuthorDisplayNameOrderbyQueryAsc() { * Test post meta number orderby query asc * * @since 1.8 + * @group single-site */ public function testSearchPostMetaNumOrderbyQueryAsc() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ), array( 'test_key' => 3 ) ); @@ -1530,6 +1562,7 @@ public function testSearchPostMetaNumOrderbyQueryAsc() { * Test post category orderby query asc * * @since 1.8 + * @group single-site */ public function testSearchTaxNameOrderbyQueryAsc() { $cat1 = wp_create_category( 'Category 1' ); @@ -1564,6 +1597,7 @@ public function testSearchTaxNameOrderbyQueryAsc() { * Test post meta number orderby query desc * * @since 1.8 + * @group single-site */ public function testSearchPostMetaNumOrderbyQueryDesc() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ), array( 'test_key' => 3 ) ); @@ -1593,6 +1627,7 @@ public function testSearchPostMetaNumOrderbyQueryDesc() { * Test post meta num multiple fields orderby query asc * * @since 1.8 + * @group single-site */ public function testSearchPostMetaNumMultipleOrderbyQuery() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 444' ), array( 'test_key' => 3, 'test_key2' => 2 ) ); @@ -1622,6 +1657,7 @@ public function testSearchPostMetaNumMultipleOrderbyQuery() { * Test post_date orderby query * * @since 1.4 + * @group single-site */ public function testSearchPostDateOrderbyQuery() { ep_create_and_sync_post( array( 'post_title' => 'ordertes 333' ) ); @@ -1653,6 +1689,7 @@ public function testSearchPostDateOrderbyQuery() { * Test post_date default order for ep_integrate query with no search * * @since 1.7 + * @group single-site */ public function testSearchPostDateOrderbyQueryEPIntegrate() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 333' ) ); @@ -1683,6 +1720,7 @@ public function testSearchPostDateOrderbyQueryEPIntegrate() { * Test relevance orderby query advanced * * @since 1.2 + * @group single-site */ public function testSearchRelevanceOrderbyQueryAdvanced() { $posts = array(); @@ -1731,6 +1769,7 @@ public function testSearchRelevanceOrderbyQueryAdvanced() { * Test relevance orderby query * * @since 1.1 + * @group single-site */ public function testSearchRelevanceOrderbyQuery() { ep_create_and_sync_post(); @@ -1756,6 +1795,7 @@ public function testSearchRelevanceOrderbyQuery() { * Test post_name orderby query * * @since 1.1 + * @group single-site */ public function testSearchPostNameOrderbyQuery() { ep_create_and_sync_post( array( 'post_title' => 'postname-ordertest-333' ) ); @@ -1786,6 +1826,7 @@ public function testSearchPostNameOrderbyQuery() { * Default is to use _score and 'desc' * * @since 1.1 + * @group single-site */ public function testSearchDefaultOrderbyQuery() { ep_create_and_sync_post(); @@ -1812,6 +1853,7 @@ public function testSearchDefaultOrderbyQuery() { * Default is to use _score orderby; using 'asc' order * * @since 1.1 + * @group single-site */ public function testSearchDefaultOrderbyASCOrderQuery() { ep_create_and_sync_post(); @@ -1837,6 +1879,7 @@ public function testSearchDefaultOrderbyASCOrderQuery() { * Test orderby random * * @since 2.1.1 + * @group single-site */ public function testRandOrderby() { ep_create_and_sync_post( array( 'post_title' => 'ordertest 1' ) ); @@ -1863,6 +1906,7 @@ public function testRandOrderby() { * Test that a post being directly deleted gets correctly removed from the Elasticsearch index * * @since 1.2 + * @group single-site */ public function testPostForceDelete() { add_action( 'ep_delete_post', array( $this, 'action_delete_post' ), 10, 0 ); @@ -1899,6 +1943,7 @@ public function testPostForceDelete() { * Test that empty search string returns all results * * @since 1.2 + * @group single-site */ public function testEmptySearchString() { ep_create_and_sync_post(); @@ -1920,6 +1965,7 @@ public function testEmptySearchString() { * Test a query that searches and filters by a meta equal query * * @since 1.3 + * @group single-site */ public function testMetaQueryEquals() { ep_create_and_sync_post( array( 'post_content' => 'the post content' ) ); @@ -1947,6 +1993,7 @@ public function testMetaQueryEquals() { * Test a query that searches and filters by a meta not equal query * * @since 1.3 + * @group single-site */ public function testMetaQueryNotEquals() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -1975,6 +2022,7 @@ public function testMetaQueryNotEquals() { * Test a query that searches and filters by a meta exists query * * @since 1.3 + * @group single-site */ public function testMetaQueryExists() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2001,6 +2049,7 @@ public function testMetaQueryExists() { /** * Test a query that searches and filters by a meta not exists query * + * @group single-site * @since 1.3 */ public function testMetaQueryNotExists() { @@ -2029,6 +2078,7 @@ public function testMetaQueryNotExists() { * Test a query that searches and filters by a meta greater than to query * * @since 1.4 + * @group single-site */ public function testMetaQueryGreaterThan() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2058,6 +2108,7 @@ public function testMetaQueryGreaterThan() { * Test a query that searches and filters by a meta between query * * @since 2.0 + * @group single-site */ public function testMetaQueryBetween() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2088,6 +2139,7 @@ public function testMetaQueryBetween() { * Test a query that searches and filters by a meta greater than or equal to query * * @since 1.4 + * @group single-site */ public function testMetaQueryGreaterThanEqual() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2117,6 +2169,7 @@ public function testMetaQueryGreaterThanEqual() { * Test a query that searches and filters by a meta less than to query * * @since 1.4 + * @group single-site */ public function testMetaQueryLessThan() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2146,6 +2199,7 @@ public function testMetaQueryLessThan() { * Test a query that searches and filters by a meta less than or equal to query * * @since 1.4 + * @group single-site */ public function testMetaQueryLessThanEqual() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2175,6 +2229,7 @@ public function testMetaQueryLessThanEqual() { * Test an advanced meta filter query * * @since 1.3 + * @group single-site */ public function testMetaQueryOrRelation() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ), array( 'test_key5' => 'value1' ) ); @@ -2208,6 +2263,7 @@ public function testMetaQueryOrRelation() { * Test an advanced meta filter query * * @since 1.3 + * @group single-site */ public function testMetaQueryAdvanced() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ), array( 'test_key' => 'value1' ) ); @@ -2243,7 +2299,9 @@ public function testMetaQueryAdvanced() { /** * Test a query that searches and filters by a meta value like the query + * * @since 1.5 + * @group single-site */ public function testMetaQueryLike() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2275,6 +2333,7 @@ public function testMetaQueryLike() { * Ensure that we do not search that post type when all post types are searched * * @since 1.3 + * @group single-site */ public function testExcludeFromSearch() { $post_ids = array(); @@ -2314,6 +2373,7 @@ public function testExcludeFromSearch() { * Test what happens when no post types are available to be searched * * @since 1.3 + * @group single-site */ public function testNoAvailablePostTypesToSearch() { $GLOBALS['wp_post_types']; @@ -2354,6 +2414,7 @@ public function testNoAvailablePostTypesToSearch() { * Test cache_results is off by default * * @since 1.5 + * @group single-site */ public function testCacheResultsDefaultOff() { ep_create_and_sync_post(); @@ -2373,6 +2434,7 @@ public function testCacheResultsDefaultOff() { * Test cache_results can be turned on * * @since 1.5 + * @group single-site */ public function testCacheResultsOn() { ep_create_and_sync_post(); @@ -2393,6 +2455,7 @@ public function testCacheResultsOn() { * Test using cache_results actually populates the cache * * @since 1.5 + * @group single-site */ public function testCachedResultIsInCache() { ep_create_and_sync_post(); @@ -2417,6 +2480,7 @@ public function testCachedResultIsInCache() { * Test setting cache results to false doesn't store anything in the cache * * @since 1.5 + * @group single-site */ public function testCachedResultIsNotInCache() { ep_create_and_sync_post(); @@ -2439,8 +2503,9 @@ public function testCachedResultIsNotInCache() { /** * Test if $post object values exist after receiving odd values from the 'ep_search_post_return_args' filter. - * @group 306 + * * @link https://github.com/10up/ElasticPress/issues/306 + * @group single-site */ public function testPostReturnArgs() { add_filter( 'ep_search_post_return_args', array( $this, 'ep_search_post_return_args_filter' ) ); @@ -2476,9 +2541,8 @@ public function mock_indexable_post_status( $post_statuses ) { /** * Test invalid post date time - * - * @param array $post_statuses - * @return array + * + * @group single-site */ public function testPostInvalidDateTime(){ add_filter( 'ep_indexable_post_status', array( $this, 'mock_indexable_post_status' ), 10, 1 ); @@ -2509,7 +2573,7 @@ public function testPostInvalidDateTime(){ * Test to verify that a post type that is set to exclude_from_search isn't indexable. * * @since 1.6 - * @link https://github.com/10up/ElasticPress/issues/321 + * @group single-site */ public function testExcludeIndexablePostType() { $post_types = ep_get_indexable_post_types(); @@ -2522,6 +2586,7 @@ public function testExcludeIndexablePostType() { * * @since 1.6 * @link https://github.com/10up/ElasticPress/issues/343 + * @group single-site */ public function testAutoDraftPostStatus() { // Let's test inserting an 'auto-draft' post. @@ -2559,6 +2624,7 @@ function _check_404( $response, $type, $class, $args, $url ) { * Test to verify meta array is built correctly. * * @since 1.7 + * @group single-site */ public function testPrepareMeta() { @@ -2628,9 +2694,8 @@ public function filter_ep_prepare_meta_excluded_public_keys( $meta_keys ) { /** * Test meta preparation * - * Tests meta perparation - * * @since 1.7 + * @group single-site */ public function testMetaValueTypes() { @@ -2662,6 +2727,7 @@ public function testMetaValueTypes() { * Test meta key query * * @since 2.1 + * @group single-site */ public function testMetaKeyQuery() { @@ -2669,6 +2735,7 @@ public function testMetaKeyQuery() { ep_create_and_sync_post( array( 'post_content' => 'post content findme' ), array( 'test_key' => 'test' ) ); ep_refresh_index(); + $args = array( 's' => 'findme', 'meta_key' => 'test_key', @@ -2686,6 +2753,7 @@ public function testMetaKeyQuery() { * Test meta key query with num * * @since 2.1 + * @group single-site */ public function testMetaKeyQueryNum() { @@ -2710,6 +2778,7 @@ public function testMetaKeyQueryNum() { * Test mix meta_key with meta_query * * @since 2.1 + * @group single-site */ public function testMetaKeyQueryMix() { @@ -2740,6 +2809,7 @@ public function testMetaKeyQueryMix() { * Test numeric integer meta queries * * @since 1.7 + * @group single-site */ public function testMetaValueTypeQueryNumeric() { @@ -2806,6 +2876,7 @@ public function testMetaValueTypeQueryNumeric() { * Test decimal meta queries * * @since 1.7 + * @group single-site */ public function testMetaValueTypeQueryDecimal() { @@ -2854,6 +2925,7 @@ public function testMetaValueTypeQueryDecimal() { * Test character meta queries. Really just defaults to a normal string query * * @since 1.7 + * @group single-site */ public function testMetaValueTypeQueryChar() { @@ -2885,6 +2957,7 @@ public function testMetaValueTypeQueryChar() { * Test date meta queries * * @since 1.7 + * @group single-site */ public function testMetaValueTypeQueryDate() { ep_create_and_sync_post( array( 'post_content' => 'post content findme' ), array( 'test_key' => '11/13/15' ) ); @@ -2930,6 +3003,7 @@ public function testMetaValueTypeQueryDate() { * Test time meta queries * * @since 1.7 + * @group single-site */ public function testMetaValueTypeQueryTime() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -2973,6 +3047,7 @@ public function testMetaValueTypeQueryTime() { * Test date time meta queries * * @since 1.7 + * @group single-site */ public function testMetaValueTypeQueryDatetime() { ep_create_and_sync_post( array( 'post_content' => 'the post content findme' ) ); @@ -3030,7 +3105,7 @@ public function testMetaValueTypeQueryDatetime() { /* * Test a post_parent query - * @group testPostParentQuery + * @group single-site * @since 2.0 */ public function testPostParentQuery() { @@ -3055,6 +3130,7 @@ public function testPostParentQuery() { * Test register feature * * @since 2.1 + * @group single-site */ public function testRegisterFeature() { ep_register_feature( 'test', array( @@ -3069,20 +3145,23 @@ public function testRegisterFeature() { * Test setup features * * @since 2.1 + * @group single-site */ public function testSetupFeatures() { + delete_option( 'ep_active_features' ); + ep_register_feature( 'test', array( 'title' => 'Test', ) ); - ep_activate_feature( 'test' ); - $feature = ep_get_registered_feature( 'test' ); $this->assertTrue( ! empty( $feature ) ); $this->assertTrue( ! $feature->is_active() ); + ep_activate_feature( 'test' ); + EP_Features::factory()->setup_features(); $this->assertTrue( $feature->is_active() ); @@ -3092,6 +3171,7 @@ public function testSetupFeatures() { * Test Tax Query NOT IN operator * * @since 2.1 + * @group single-site */ public function testTaxQueryNotIn() { ep_create_and_sync_post( array( 'post_content' => 'findme test 1', 'tags_input' => array( 'one', 'two' ) ) ); From eee42c1c11da0d5bb1f712dc9b2df2e8e24d41db Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 01:15:17 -0500 Subject: [PATCH 087/159] Clone post type before modifying it in test --- tests/test-single-site.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index 9646b0546a..dd69666ed7 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -2381,7 +2381,9 @@ public function testNoAvailablePostTypesToSearch() { $backup_post_types = $GLOBALS['wp_post_types']; // Set all post types to be excluded from search - foreach ( $GLOBALS['wp_post_types'] as $post_type ) { + foreach ( $GLOBALS['wp_post_types'] as $key => $post_type ) { + $backup_post_types[ $key ] = clone $post_type; + $post_type->exclude_from_search = true; } From 29edf06090798b3b4ab216c94c78bc426ef40e7b Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 01:19:12 -0500 Subject: [PATCH 088/159] Update travis file php version, formatting, remove code climate --- .travis.yml | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 933a2f469f..087b8ae6fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,29 +1,20 @@ language: php php: - - "5.6" - - "5.3" - - "5.2" + - "7.0" + - "5.6" + - "5.2" env: - - WP_VERSION=latest WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=1 - - WP_VERSION=3.7.1 WP_MULTISITE=0 - - WP_VERSION=3.7.1 WP_MULTISITE=1 + - WP_VERSION=latest WP_MULTISITE=0 + - WP_VERSION=latest WP_MULTISITE=1 + - WP_VERSION=3.7.1 WP_MULTISITE=0 + - WP_VERSION=3.7.1 WP_MULTISITE=1 services: - - elasticsearch + - elasticsearch before_script: - - mkdir -p build/logs - - if [ "$(phpenv version-name)" != "5.2" ]; then composer install --dev; fi; - - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION - - sleep 5 - - if [[ $TRAVIS_PHP_VERSION != 'hhvm' ]]; then phpenv config-rm xdebug.ini; fi - -script: - - if [ "$(phpenv version-name)" != "5.2" ]; then phpunit --coverage-clover build/logs/clover.xml; else phpunit; fi; - -after_script: - - ./vendor/bin/test-reporter --stdout > codeclimate.json - - "if [ \"$(phpenv version-name)\" != \"5.2\" ]; then curl -X POST -d @codeclimate.json -H 'Content-Type: application/json' -H 'User-Agent: Code Climate (PHP Test Reporter v0.1.1)' https://codeclimate.com/test_reports; fi;" + - if [ "$(phpenv version-name)" != "5.2" ]; then composer install --dev; fi; + - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + - sleep 10 From b7f3603488aab49e007f464b49dd6a0f468c015e Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 01:22:50 -0500 Subject: [PATCH 089/159] Remove php 5.2 from travis --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 087b8ae6fa..7989cf5f72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,7 @@ language: php php: - "7.0" - - "5.6" - - "5.2" + - "5.3" env: - WP_VERSION=latest WP_MULTISITE=0 From cc810bbd6f7952de2ded71318a616a667594b151 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 09:50:07 -0500 Subject: [PATCH 090/159] Make phpunit.xml .dist; refresh install-wp-tests.sh from wpcli; proper travis notifications --- .travis.yml | 5 +++++ bin/install-wp-tests.sh | 39 ++++++++++++++++++++++----------- phpunit.xml => phpunit.xml.dist | 0 3 files changed, 31 insertions(+), 13 deletions(-) mode change 100644 => 100755 bin/install-wp-tests.sh rename phpunit.xml => phpunit.xml.dist (100%) diff --git a/.travis.yml b/.travis.yml index 7989cf5f72..3c61ce0764 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ language: php +notifications: + email: + on_success: never + on_failure: change + php: - "7.0" - "5.3" diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh old mode 100644 new mode 100755 index 6849a1b6a4..73bb4c787e --- a/bin/install-wp-tests.sh +++ b/bin/install-wp-tests.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash if [ $# -lt 3 ]; then - echo "usage: $0 [db-host] [wp-version]" + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" exit 1 fi @@ -10,6 +10,7 @@ DB_USER=$2 DB_PASS=$3 DB_HOST=${4-localhost} WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} @@ -24,6 +25,8 @@ download() { if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then WP_TESTS_TAG="tags/$WP_VERSION" +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" else # http serves a single offer, whereas https serves multiple. we only want one download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json @@ -46,18 +49,22 @@ install_wp() { mkdir -p $WP_CORE_DIR - if [ $WP_VERSION == 'latest' ]; then - local ARCHIVE_NAME='latest' + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p /tmp/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip + unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ + mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR else - local ARCHIVE_NAME="wordpress-$WP_VERSION" + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz + tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR fi - download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz - tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR - - if [ $WP_VERSION == '3.7.1' ]; then - download https://raw.githubusercontent.com/markoheijnen/wp-mysqli/master/db-legacy.php $WP_CORE_DIR/wp-content/db.php - fi + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php } install_test_suite() { @@ -73,13 +80,14 @@ install_test_suite() { # set up testing suite mkdir -p $WP_TESTS_DIR svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data fi - cd $WP_TESTS_DIR - if [ ! -f wp-tests-config.php ]; then download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php @@ -89,6 +97,11 @@ install_test_suite() { } install_db() { + + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + # parse DB_HOST for port or socket references local PARTS=(${DB_HOST//\:/ }) local DB_HOSTNAME=${PARTS[0]}; diff --git a/phpunit.xml b/phpunit.xml.dist similarity index 100% rename from phpunit.xml rename to phpunit.xml.dist From 55406a64ae8f272e5e12d54576ca465567dcb30d Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 10:56:28 -0500 Subject: [PATCH 091/159] Only test latest version; no php 5.2 --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c61ce0764..717d896c5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,12 @@ php: - "5.3" env: - - WP_VERSION=latest WP_MULTISITE=0 - WP_VERSION=latest WP_MULTISITE=1 - - WP_VERSION=3.7.1 WP_MULTISITE=0 - - WP_VERSION=3.7.1 WP_MULTISITE=1 services: - elasticsearch before_script: - - if [ "$(phpenv version-name)" != "5.2" ]; then composer install --dev; fi; + - composer install --dev - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION - sleep 10 From b4085b99879124e03abf68ea6516f98fb34673bc Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 11:07:10 -0500 Subject: [PATCH 092/159] Fix 5.3 error --- tests/test-single-site.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index dd69666ed7..0b3d05344a 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -2383,7 +2383,7 @@ public function testNoAvailablePostTypesToSearch() { // Set all post types to be excluded from search foreach ( $GLOBALS['wp_post_types'] as $key => $post_type ) { $backup_post_types[ $key ] = clone $post_type; - + $post_type->exclude_from_search = true; } @@ -3139,8 +3139,10 @@ public function testRegisterFeature() { 'title' => 'Test', ) ); + $feature = ep_get_registered_feature( 'test' ); + $this->assertTrue( ! empty( EP_Features::factory()->registered_features['test'] ) ); - $this->assertTrue( ! empty( ep_get_registered_feature( 'test' ) ) ); + $this->assertTrue( ! empty( $feature ) ); } /** From 097cecbda438304ef9de5741f3aa27bd0c0264f0 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 11:37:53 -0500 Subject: [PATCH 093/159] Update composer lock; show WC and WP versions --- composer.lock | 412 ++++++++++++++++++++++++++------------------ tests/bootstrap.php | 6 +- 2 files changed, 251 insertions(+), 167 deletions(-) diff --git a/composer.lock b/composer.lock index ec9ecb6bb8..c4d29b4ec0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,22 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "5acefaef01f73297640f746393d88738", "content-hash": "cddfe9d6a35a7fd274871696c0c65143", "packages": [], "packages-dev": [ { "name": "composer/ca-bundle", - "version": "1.0.3", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "5df9ed0ed0c9506ea6404a23450854e5df15cc12" + "reference": "a795611394b3c05164fd0eb291b492b39339cba4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/5df9ed0ed0c9506ea6404a23450854e5df15cc12", - "reference": "5df9ed0ed0c9506ea6404a23450854e5df15cc12", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/a795611394b3c05164fd0eb291b492b39339cba4", + "reference": "a795611394b3c05164fd0eb291b492b39339cba4", "shasum": "" }, "require": { @@ -28,6 +27,7 @@ "php": "^5.3.2 || ^7.0" }, "require-dev": { + "psr/log": "^1.0", "symfony/process": "^2.5 || ^3.0" }, "suggest": { @@ -63,36 +63,36 @@ "ssl", "tls" ], - "time": "2016-07-18 23:07:53" + "time": "2016-11-02T18:11:27+00:00" }, { "name": "composer/composer", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "b49a006748a460f8dae6500ec80ed021501ce969" + "reference": "e53f9e5381e70f76e098136343e27d92601eade7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/b49a006748a460f8dae6500ec80ed021501ce969", - "reference": "b49a006748a460f8dae6500ec80ed021501ce969", + "url": "https://api.github.com/repos/composer/composer/zipball/e53f9e5381e70f76e098136343e27d92601eade7", + "reference": "e53f9e5381e70f76e098136343e27d92601eade7", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0", "composer/semver": "^1.0", "composer/spdx-licenses": "^1.0", - "justinrainbow/json-schema": "^1.6 || ^2.0", + "justinrainbow/json-schema": "^1.6 || ^2.0 || ^3.0 || ^4.0", "php": "^5.3.2 || ^7.0", "psr/log": "^1.0", "seld/cli-prompt": "^1.0", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.0", - "symfony/console": "^2.5 || ^3.0", - "symfony/filesystem": "^2.5 || ^3.0", - "symfony/finder": "^2.2 || ^3.0", - "symfony/process": "^2.1 || ^3.0" + "symfony/console": "^2.7 || ^3.0", + "symfony/filesystem": "^2.7 || ^3.0", + "symfony/finder": "^2.7 || ^3.0", + "symfony/process": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^4.5 || ^5.0.5", @@ -109,7 +109,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -140,20 +140,20 @@ "dependency", "package" ], - "time": "2016-07-18 23:28:52" + "time": "2016-12-23T23:47:04+00:00" }, { "name": "composer/installers", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "a3595c5272a6f247228abb20076ed27321e4aae9" + "reference": "d78064c68299743e0161004f2de3a0204e33b804" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/a3595c5272a6f247228abb20076ed27321e4aae9", - "reference": "a3595c5272a6f247228abb20076ed27321e4aae9", + "url": "https://api.github.com/repos/composer/installers/zipball/d78064c68299743e0161004f2de3a0204e33b804", + "reference": "d78064c68299743e0161004f2de3a0204e33b804", "shasum": "" }, "require": { @@ -200,6 +200,7 @@ "MODX Evo", "Mautic", "OXID", + "Plentymarkets", "RadPHP", "SMF", "Thelia", @@ -207,9 +208,11 @@ "agl", "aimeos", "annotatecms", + "attogram", "bitrix", "cakephp", "chef", + "cockpit", "codeigniter", "concrete5", "croogo", @@ -233,29 +236,31 @@ "piwik", "ppi", "puppet", + "reindex", "roundcube", "shopware", "silverstripe", "symfony", "typo3", "wordpress", + "yawik", "zend", "zikula" ], - "time": "2016-07-05 06:18:20" + "time": "2016-08-13T20:53:52+00:00" }, { "name": "composer/semver", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "03c9de5aa25e7672c4ad251eeaba0c47a06c8b98" + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/03c9de5aa25e7672c4ad251eeaba0c47a06c8b98", - "reference": "03c9de5aa25e7672c4ad251eeaba0c47a06c8b98", + "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", "shasum": "" }, "require": { @@ -304,20 +309,20 @@ "validation", "versioning" ], - "time": "2016-06-02 09:04:51" + "time": "2016-08-30T16:08:34+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88" + "reference": "96c6a07b05b716e89a44529d060bc7f5c263cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/88c26372b1afac36d8db601cdf04ad8716f53d88", - "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/96c6a07b05b716e89a44529d060bc7f5c263cb13", + "reference": "96c6a07b05b716e89a44529d060bc7f5c263cb13", "shasum": "" }, "require": { @@ -365,7 +370,7 @@ "spdx", "validator" ], - "time": "2016-05-04 12:27:30" + "time": "2016-09-28T07:17:45+00:00" }, { "name": "doctrine/instantiator", @@ -419,20 +424,20 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2015-06-14T21:17:01+00:00" }, { "name": "justinrainbow/json-schema", - "version": "2.0.5", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "6b2a33e6a768f96bdc2ead5600af0822eed17d67" + "reference": "d39c56a46b3ebe1f3696479966cd2b9f50aaa24f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/6b2a33e6a768f96bdc2ead5600af0822eed17d67", - "reference": "6b2a33e6a768f96bdc2ead5600af0822eed17d67", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/d39c56a46b3ebe1f3696479966cd2b9f50aaa24f", + "reference": "d39c56a46b3ebe1f3696479966cd2b9f50aaa24f", "shasum": "" }, "require": { @@ -449,7 +454,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -485,7 +490,7 @@ "json", "schema" ], - "time": "2016-06-02 10:59:52" + "time": "2016-12-22T16:43:46+00:00" }, { "name": "mustache/mustache", @@ -531,25 +536,28 @@ "mustache", "templating" ], - "time": "2016-07-31 06:18:27" + "time": "2016-07-31T06:18:27+00:00" }, { "name": "mustangostang/spyc", - "version": "0.5.1", + "version": "0.6.1", "source": { "type": "git", "url": "https://github.com/mustangostang/spyc.git", - "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977" + "reference": "022532641d61d383fd3ae666982bd46e61e5915e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mustangostang/spyc/zipball/dc4785b4d7227fd9905e086d499fb8abfadf9977", - "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/022532641d61d383fd3ae666982bd46e61e5915e", + "reference": "022532641d61d383fd3ae666982bd46e61e5915e", "shasum": "" }, "require": { "php": ">=5.3.1" }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, "type": "library", "extra": { "branch-alias": { @@ -563,7 +571,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT License" + "MIT" ], "authors": [ { @@ -578,7 +586,7 @@ "yaml", "yml" ], - "time": "2013-02-21 10:52:01" + "time": "2016-10-21T00:03:34+00:00" }, { "name": "nb/oxymel", @@ -619,7 +627,7 @@ "keywords": [ "xml" ], - "time": "2013-02-24 15:01:54" + "time": "2013-02-24T15:01:54+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -668,20 +676,20 @@ "email": "mike.vanriel@naenius.com" } ], - "time": "2015-02-03 12:10:50" + "time": "2015-02-03T12:10:50+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.6.1", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", + "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", "shasum": "" }, "require": { @@ -689,10 +697,11 @@ "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0" + "sebastian/recursion-context": "^1.0|^2.0" }, "require-dev": { - "phpspec/phpspec": "^2.0" + "phpspec/phpspec": "^2.0", + "phpunit/phpunit": "^4.8 || ^5.6.5" }, "type": "library", "extra": { @@ -730,7 +739,7 @@ "spy", "stub" ], - "time": "2016-06-07 08:13:47" + "time": "2016-11-21T14:58:47+00:00" }, { "name": "phpunit/php-code-coverage", @@ -792,20 +801,20 @@ "testing", "xunit" ], - "time": "2015-10-06 15:47:00" + "time": "2015-10-06T15:47:00+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", "shasum": "" }, "require": { @@ -839,7 +848,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2016-10-03T07:40:28+00:00" }, { "name": "phpunit/php-text-template", @@ -880,7 +889,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -924,20 +933,20 @@ "keywords": [ "timer" ], - "time": "2016-05-12 18:03:57" + "time": "2016-05-12T18:03:57+00:00" }, { "name": "phpunit/php-token-stream", - "version": "1.4.8", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b", + "reference": "3b402f65a4cc90abf6e1104e388b896ce209631b", "shasum": "" }, "require": { @@ -973,20 +982,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-09-15 10:49:45" + "time": "2016-11-15T14:06:22+00:00" }, { "name": "phpunit/phpunit", - "version": "4.8.27", + "version": "4.8.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90" + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/98b2b39a520766bec663ff5b7ff1b729db9dbfe3", + "reference": "98b2b39a520766bec663ff5b7ff1b729db9dbfe3", "shasum": "" }, "require": { @@ -1002,7 +1011,7 @@ "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.1", + "sebastian/comparator": "~1.2.2", "sebastian/diff": "~1.2", "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", @@ -1045,7 +1054,7 @@ "testing", "xunit" ], - "time": "2016-07-21 06:48:14" + "time": "2016-12-09T02:45:31+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -1101,26 +1110,34 @@ "mock", "xunit" ], - "time": "2015-10-02 06:51:40" + "time": "2015-10-02T06:51:40+00:00" }, { "name": "psr/log", - "version": "1.0.0", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "shasum": "" }, + "require": { + "php": ">=5.3.0" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { - "psr-0": { - "Psr\\Log\\": "" + "psr-4": { + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1134,12 +1151,13 @@ } ], "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ "log", "psr", "psr-3" ], - "time": "2012-12-21 11:40:51" + "time": "2016-10-10T12:19:37+00:00" }, { "name": "ramsey/array_column", @@ -1184,27 +1202,27 @@ "array_column", "column" ], - "time": "2015-03-20 22:07:39" + "time": "2015-03-20T22:07:39+00:00" }, { "name": "rmccue/requests", - "version": "v1.6.1", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/rmccue/Requests.git", - "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea" + "reference": "87932f52ffad70504d93f04f15690cf16a089546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rmccue/Requests/zipball/6aac485666c2955077d77b796bbdd25f0013a4ea", - "reference": "6aac485666c2955077d77b796bbdd25f0013a4ea", + "url": "https://api.github.com/repos/rmccue/Requests/zipball/87932f52ffad70504d93f04f15690cf16a089546", + "reference": "87932f52ffad70504d93f04f15690cf16a089546", "shasum": "" }, "require": { "php": ">=5.2" }, "require-dev": { - "satooshi/php-coveralls": "dev-master" + "requests/test-server": "dev-master" }, "type": "library", "autoload": { @@ -1233,26 +1251,26 @@ "iri", "sockets" ], - "time": "2014-05-18 04:59:02" + "time": "2016-10-13T00:11:37+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.0", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", + "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", "shasum": "" }, "require": { "php": ">=5.3.3", "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" + "sebastian/exporter": "~1.2 || ~2.0" }, "require-dev": { "phpunit/phpunit": "~4.4" @@ -1297,7 +1315,7 @@ "compare", "equality" ], - "time": "2015-07-26 15:48:44" + "time": "2016-11-19T09:18:40+00:00" }, { "name": "sebastian/diff", @@ -1349,7 +1367,7 @@ "keywords": [ "diff" ], - "time": "2015-12-08 07:14:41" + "time": "2015-12-08T07:14:41+00:00" }, { "name": "sebastian/environment", @@ -1399,7 +1417,7 @@ "environment", "hhvm" ], - "time": "2016-08-18 05:49:44" + "time": "2016-08-18T05:49:44+00:00" }, { "name": "sebastian/exporter", @@ -1466,7 +1484,7 @@ "export", "exporter" ], - "time": "2016-06-17 09:04:28" + "time": "2016-06-17T09:04:28+00:00" }, { "name": "sebastian/global-state", @@ -1517,7 +1535,7 @@ "keywords": [ "global state" ], - "time": "2015-10-12 03:26:01" + "time": "2015-10-12T03:26:01+00:00" }, { "name": "sebastian/recursion-context", @@ -1570,7 +1588,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" + "time": "2015-11-11T19:50:13+00:00" }, { "name": "sebastian/version", @@ -1605,7 +1623,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21 13:59:46" + "time": "2015-06-21T13:59:46+00:00" }, { "name": "seld/cli-prompt", @@ -1653,20 +1671,20 @@ "input", "prompt" ], - "time": "2016-04-18 09:31:41" + "time": "2016-04-18T09:31:41+00:00" }, { "name": "seld/jsonlint", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "66834d3e3566bb5798db7294619388786ae99394" + "reference": "19495c181d6d53a0a13414154e52817e3b504189" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/66834d3e3566bb5798db7294619388786ae99394", - "reference": "66834d3e3566bb5798db7294619388786ae99394", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/19495c181d6d53a0a13414154e52817e3b504189", + "reference": "19495c181d6d53a0a13414154e52817e3b504189", "shasum": "" }, "require": { @@ -1699,7 +1717,7 @@ "parser", "validator" ], - "time": "2015-11-21 02:21:41" + "time": "2016-11-14T17:59:58+00:00" }, { "name": "seld/phar-utils", @@ -1743,26 +1761,29 @@ "keywords": [ "phra" ], - "time": "2015-10-13 18:44:15" + "time": "2015-10-13T18:44:15+00:00" }, { "name": "symfony/config", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4275ef5b59f18959df0eee3991e9ca0cc208ffd4" + "reference": "b522856007b258f46d5ee35d3b7b235c11e76e86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4275ef5b59f18959df0eee3991e9ca0cc208ffd4", - "reference": "4275ef5b59f18959df0eee3991e9ca0cc208ffd4", + "url": "https://api.github.com/repos/symfony/config/zipball/b522856007b258f46d5ee35d3b7b235c11e76e86", + "reference": "b522856007b258f46d5ee35d3b7b235c11e76e86", "shasum": "" }, "require": { "php": ">=5.3.9", "symfony/filesystem": "~2.3|~3.0.0" }, + "require-dev": { + "symfony/yaml": "~2.7|~3.0.0" + }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" }, @@ -1796,24 +1817,25 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2016-07-26 08:02:44" + "time": "2016-12-10T08:21:45+00:00" }, { "name": "symfony/console", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "36e62335caca8a6e909c5c5bac4a8128149911c9" + "reference": "d5643cd095e5e37d31e004bb2606b5dd7e96602f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/36e62335caca8a6e909c5c5bac4a8128149911c9", - "reference": "36e62335caca8a6e909c5c5bac4a8128149911c9", + "url": "https://api.github.com/repos/symfony/console/zipball/d5643cd095e5e37d31e004bb2606b5dd7e96602f", + "reference": "d5643cd095e5e37d31e004bb2606b5dd7e96602f", "shasum": "" }, "require": { "php": ">=5.3.9", + "symfony/debug": "~2.7,>=2.7.2|~3.0.0", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { @@ -1856,20 +1878,77 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-07-30 07:20:35" + "time": "2016-12-06T11:59:35+00:00" + }, + { + "name": "symfony/debug", + "version": "v2.8.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "62a68f640456f6761d752c62d81631428ef0d8a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/62a68f640456f6761d752c62d81631428ef0d8a1", + "reference": "62a68f640456f6761d752c62d81631428ef0d8a1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|~2.6,>=2.6.2|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2016-11-15T12:53:17+00:00" }, { "name": "symfony/dependency-injection", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12" + "reference": "51a7b5385fb0f42e5edbdb2cfbad1a011ecdaee7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f2b5a00d176f6a201dc430375c0ef37706ea3d12", - "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/51a7b5385fb0f42e5edbdb2cfbad1a011ecdaee7", + "reference": "51a7b5385fb0f42e5edbdb2cfbad1a011ecdaee7", "shasum": "" }, "require": { @@ -1919,20 +1998,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2016-07-30 07:20:35" + "time": "2016-12-08T14:41:31+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8" + "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8", - "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", + "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", "shasum": "" }, "require": { @@ -1979,20 +2058,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-07-28 16:56:28" + "time": "2016-10-13T01:43:15+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "ab4c3f085c8f5a56536845bf985c4cef30bf75fd" + "reference": "a3784111af9f95f102b6411548376e1ae7c93898" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ab4c3f085c8f5a56536845bf985c4cef30bf75fd", - "reference": "ab4c3f085c8f5a56536845bf985c4cef30bf75fd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a3784111af9f95f102b6411548376e1ae7c93898", + "reference": "a3784111af9f95f102b6411548376e1ae7c93898", "shasum": "" }, "require": { @@ -2028,20 +2107,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2016-07-20 05:41:28" + "time": "2016-10-18T04:28:30+00:00" }, { "name": "symfony/finder", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "60804d88691e4a73bbbb3035eb1d9f075c5c2c10" + "reference": "c0f10576335743b881ac1ed39d18c0fa66048775" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/60804d88691e4a73bbbb3035eb1d9f075c5c2c10", - "reference": "60804d88691e4a73bbbb3035eb1d9f075c5c2c10", + "url": "https://api.github.com/repos/symfony/finder/zipball/c0f10576335743b881ac1ed39d18c0fa66048775", + "reference": "c0f10576335743b881ac1ed39d18c0fa66048775", "shasum": "" }, "require": { @@ -2077,20 +2156,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-07-26 08:02:44" + "time": "2016-12-13T09:38:12+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "dff51f72b0706335131b00a7f49606168c582594" + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", - "reference": "dff51f72b0706335131b00a7f49606168c582594", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", "shasum": "" }, "require": { @@ -2102,7 +2181,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -2136,20 +2215,20 @@ "portable", "shim" ], - "time": "2016-05-18 14:26:46" + "time": "2016-11-14T01:06:16+00:00" }, { "name": "symfony/process", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d20332e43e8774ff8870b394f3dd6020cc7f8e0c" + "reference": "1a1bd056395540d0bc549d39818316513565d278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d20332e43e8774ff8870b394f3dd6020cc7f8e0c", - "reference": "d20332e43e8774ff8870b394f3dd6020cc7f8e0c", + "url": "https://api.github.com/repos/symfony/process/zipball/1a1bd056395540d0bc549d39818316513565d278", + "reference": "1a1bd056395540d0bc549d39818316513565d278", "shasum": "" }, "require": { @@ -2185,20 +2264,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-07-28 11:13:19" + "time": "2016-11-24T00:43:03+00:00" }, { "name": "symfony/translation", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "32b0c824da6df065f43b0c458dc505940e98a7f1" + "reference": "edbe67e8f729885b55421d5cc58223d48540df07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/32b0c824da6df065f43b0c458dc505940e98a7f1", - "reference": "32b0c824da6df065f43b0c458dc505940e98a7f1", + "url": "https://api.github.com/repos/symfony/translation/zipball/edbe67e8f729885b55421d5cc58223d48540df07", + "reference": "edbe67e8f729885b55421d5cc58223d48540df07", "shasum": "" }, "require": { @@ -2249,20 +2328,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2016-07-30 07:20:35" + "time": "2016-11-18T21:10:01+00:00" }, { "name": "symfony/yaml", - "version": "v2.8.9", + "version": "v2.8.15", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0ceab136f43ed9d3e97b3eea32a7855dc50c121d" + "reference": "befb26a3713c97af90d25dd12e75621ef14d91ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0ceab136f43ed9d3e97b3eea32a7855dc50c121d", - "reference": "0ceab136f43ed9d3e97b3eea32a7855dc50c121d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/befb26a3713c97af90d25dd12e75621ef14d91ff", + "reference": "befb26a3713c97af90d25dd12e75621ef14d91ff", "shasum": "" }, "require": { @@ -2298,7 +2377,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-07-17 09:06:15" + "time": "2016-11-14T16:15:57+00:00" }, { "name": "wp-cli/php-cli-tools", @@ -2348,20 +2427,20 @@ "cli", "console" ], - "time": "2016-02-08 14:34:01" + "time": "2016-02-08T14:34:01+00:00" }, { "name": "wp-cli/wp-cli", - "version": "v0.24.1", + "version": "v1.0.0", "source": { "type": "git", "url": "https://github.com/wp-cli/wp-cli.git", - "reference": "97424dc18431a2d4d10e590157b57b9ea4a88da4" + "reference": "62d10c72aa1e77f7bf0a84dd495369c41fe12a2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/97424dc18431a2d4d10e590157b57b9ea4a88da4", - "reference": "97424dc18431a2d4d10e590157b57b9ea4a88da4", + "url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/62d10c72aa1e77f7bf0a84dd495369c41fe12a2e", + "reference": "62d10c72aa1e77f7bf0a84dd495369c41fe12a2e", "shasum": "" }, "require": { @@ -2375,6 +2454,7 @@ "rmccue/requests": "~1.6", "symfony/config": "~2.7", "symfony/console": "~2.7", + "symfony/debug": "~2.7", "symfony/dependency-injection": "~2.7", "symfony/event-dispatcher": "~2.7", "symfony/filesystem": "~2.7", @@ -2414,7 +2494,7 @@ "cli", "wordpress" ], - "time": "2016-08-09 13:25:56" + "time": "2016-11-29T19:33:53+00:00" }, { "name": "wpackagist-plugin/woocommerce", @@ -2435,7 +2515,7 @@ }, "type": "wordpress-plugin", "homepage": "https://wordpress.org/plugins/woocommerce/", - "time": "2016-07-26 17:31:18" + "time": "2016-07-26T17:31:18+00:00" } ], "aliases": [], diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0dcaf77c7c..498256250a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -18,6 +18,8 @@ function ep_test_shard_number( $mapping ) { } function _manually_load_plugin() { + global $wp_version; + $host = getenv( 'EP_HOST' ); if ( empty( $host ) ) { $host = 'http://localhost:9200'; @@ -52,6 +54,8 @@ function _manually_load_plugin() { } require_once( dirname( __FILE__ ) . '/includes/functions.php' ); + + echo 'WordPress version ' . $wp_version . "\n"; } tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); @@ -64,7 +68,7 @@ function _setup_theme() { $GLOBALS['wp_roles'] = new WP_Roles(); - echo "Installing WooCommerce..." . PHP_EOL; + echo 'Installing WooCommerce version ' . WC()->version . ' ...' . PHP_EOL; } tests_add_filter( 'setup_theme', '_setup_theme' ); From e714b6be47415bd723a5f3a5ad9c36ce06842e53 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Thu, 5 Jan 2017 04:16:13 +0530 Subject: [PATCH 094/159] Fix indexable post statuses in admin feature #668 --- features/admin/admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/admin/admin.php b/features/admin/admin.php index 515c974f3b..e0cefeabf6 100644 --- a/features/admin/admin.php +++ b/features/admin/admin.php @@ -13,8 +13,8 @@ * @since 2.1 */ function ep_admin_setup() { + add_filter( 'ep_indexable_post_status', 'ep_admin_get_statuses' ); if ( is_admin() ) { - add_filter( 'ep_indexable_post_status', 'ep_admin_get_statuses' ); add_filter( 'ep_admin_wp_query_integration', '__return_true' ); add_action( 'pre_get_posts', 'ep_admin_integrate' ); } From 3cacc61293f8c49716733cdcf4d34e989e91c7ee Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 22:03:32 -0500 Subject: [PATCH 095/159] Change travis php versions --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 717d896c5c..dde35cbf2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ notifications: php: - "7.0" - - "5.3" + - "5.5" env: - WP_VERSION=latest WP_MULTISITE=1 From ea7166c96ffff675cd688bd473e3a35f62a8d58a Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 4 Jan 2017 22:09:52 -0500 Subject: [PATCH 096/159] Change travis readme branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b39b2022b0..17d121d0da 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -ElasticPress [![Build Status](https://travis-ci.org/10up/ElasticPress.svg?branch=master)](https://travis-ci.org/10up/ElasticPress) +ElasticPress [![Build Status](https://travis-ci.org/10up/ElasticPress.svg?branch=develop)](https://travis-ci.org/10up/ElasticPress) ============= A fast and flexible search and query engine for WordPress. From 02180163a79271250953940387ad92a132943130 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 5 Jan 2017 01:05:46 -0500 Subject: [PATCH 097/159] Upgrade notice --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b39b2022b0..0dfce49c0a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A fast and flexible search and query engine for WordPress. **Please note:** master is the stable branch -**Upgrade Notice:** Versions 1.6.1, 1.6.2, 1.7, 1.8, 2.1, 2.1.2 require re-syncing. +**Upgrade Notice:** Versions 1.6.1, 1.6.2, 1.7, 1.8, 2.1, 2.1.2, 2.2 require re-syncing. ElasticPress, a fast and flexible search and query engine for WordPress, enables WordPress to find or “query” relevant content extremely fast through a variety of highly customizable features. WordPress out-of-the-box struggles to analyze content relevancy and can be very slow. ElasticPress supercharges your WordPress website making for happier users and administrators. The plugin even contains features for popular plugins. From 009c702d4bb8843231873deb94a0175d152dd122 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 5 Jan 2017 01:14:04 -0500 Subject: [PATCH 098/159] 2.2 update --- readme.txt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index 1c21d5d6f1..7a155a4a3c 100644 --- a/readme.txt +++ b/readme.txt @@ -34,13 +34,25 @@ Please refer to [Github](https://github.com/10up/ElasticPress) for detailed usag == Changelog == -= 2.2 = += 2.2 (Requires re-index) = -Version 2.2 rethinks the module process to make ElasticPress a more complete query engine solution. Modules are now auto-on and really just features. Why would anyone want to not use amazing functionality to improve speed and relevancy on their website? Modules can of course be overriden and disabled. Also, modules that don't have their minimum requirements met are still auto-disabled. +Version 2.2 rethinks the module process to make ElasticPress a more complete query engine solution. Modules are now auto-on and really just features. Why would anyone want to not use amazing functionality that improves speed and relevancy on their website? Features (previously modules) can of course be overriden and disabled. Also, features that don't have their minimum requirements met are still auto-disabled. ### Enhancements + * (Breaking change) Module registration API changed. See `register_module` in `classes/class-ep-modules.php`. * (Breaking change) Related posts are now in a widget instead of automatically being appending to content. +* Admin warning if current Elasticsearch version is not between the min/max supported version. Version 2.2 supports versions 1.3 - 5.1. +* Auto-reindex on versions requiring reindex. +* User friendly admin notifications for ElasticPress not set up, first sync needed, and feature auto activation. + +### Bug Fixes + +* Back compat with old `ep_search` function. +* Respect indexable post types in WooCommerce feature +* New product drafts not showing in WooCommerce admin list +* WooCommerce feature breaking image search in media library. Props (Ritesh-patel)[https://github.com/Ritesh-patel] +* WooCommerce order search broken = 2.1.2 (Requires re-index) = From 8a77eaa93b58b3877cfb175caa752def7127fef6 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Thu, 5 Jan 2017 23:01:34 +0530 Subject: [PATCH 099/159] add support for post_mime_type #670 --- classes/class-ep-api.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 5e1fa6897a..4e96d0d8f1 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1146,6 +1146,35 @@ public function format_args( $args ) { $use_filters = true; } + + /** + * Add support for post_mime_type + * + * If we have array, it will be fool text search filter. + * If we have string(like filter images in media screen), we will have mime type "image" so need to check it as + * regexp filter. + * + * @since 2.3 + */ + if( ! empty( $args['post_mime_type'] ) ) { + if( is_array( $args['post_mime_type'] ) ) { + $filter['bool']['must'][] = array( + 'terms' => array( + 'post_mime_type' => (array)$args['post_mime_type'], + ), + ); + + $use_filters = true; + } elseif( is_string( $args['post_mime_type'] ) ) { + $filter['bool']['must'][] = array( + 'regexp' => array( + 'post_mime_type' => $args['post_mime_type'] . ".*", + ), + ); + + $use_filters = true; + } + } /** * Simple date params support From 313799484de657e23807ff97e191327184203d91 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Fri, 6 Jan 2017 02:06:14 +0530 Subject: [PATCH 100/159] add unit test for post_mime_type query #670 --- tests/test-single-site.php | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/test-single-site.php b/tests/test-single-site.php index 0b3d05344a..a3845a1b76 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -3221,4 +3221,42 @@ public function testTaxQueryNotIn() { $this->assertEquals( 1, $query->post_count ); $this->assertEquals( 1, $query->found_posts ); } + + /** + * Test post_mime_type query + * + * @since 2.3 + */ + function testPostMimeTypeQuery() { + ep_create_and_sync_post( array( 'post_type' => 'attachment', 'post_mime_type' => 'image/jpeg', 'post_status' => 'inherit' ) ); + ep_create_and_sync_post( array( 'post_type' => 'attachment', 'post_mime_type' => 'image/jpeg', 'post_status' => 'inherit' ) ); + ep_create_and_sync_post( array( 'post_type' => 'attachment', 'post_mime_type' => 'application/pdf', 'post_status' => 'inherit' ) ); + + ep_refresh_index(); + + $args = array( + 'ep_integrate' => true, + 'post_mime_type' => 'image', + 'post_type' => 'attachment', + 'post_status' => 'inherit' + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 2, $query->post_count ); + + $args = array( + 'ep_integrate' => true, + 'post_mime_type' => array( + 'image/jpeg', + 'application/pdf', + ), + 'post_type' => 'attachment', + 'post_status' => 'inherit' + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 3, $query->found_posts ); + } } From cd1fa31089f9545de74e1e704068d768e45faed3 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 6 Jan 2017 11:53:39 -0500 Subject: [PATCH 101/159] Only reindex if feature requires it --- classes/class-ep-dashboard.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index f1bb05f8e4..e5686db6fc 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -615,7 +615,9 @@ public function action_wp_ajax_ep_save_feature() { ); if ( $feature_settings[ $_POST['feature'] ]['active'] && ! $original_state ) { - $data['reindex'] = true; + if ( ! empty( $feature->requires_install_reindex ) ) { + $data['reindex'] = true; + } } wp_send_json_success( $data ); From 64c0761c330047b5d74f5c54cc9bb3878fd74aee Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 6 Jan 2017 12:22:17 -0500 Subject: [PATCH 102/159] Add action for setting up features; change feature setup priority --- classes/class-ep-features.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index 8da82d8c47..afaf021b2a 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -26,8 +26,8 @@ class EP_Features { * @since 2.1 */ public function setup() { - add_action( 'plugins_loaded', array( $this, 'handle_feature_activation' ), 5 ); - add_action( 'plugins_loaded', array( $this, 'setup_features' ), 5 ); + add_action( 'plugins_loaded', array( $this, 'handle_feature_activation' ), 12 ); + add_action( 'plugins_loaded', array( $this, 'setup_features' ), 11 ); } /** @@ -202,6 +202,8 @@ public function handle_feature_activation() { * @since 2.1 */ public function setup_features() { + do_action( 'ep_setup_features' ); + foreach ( $this->registered_features as $feature_slug => $feature ) { if ( $feature->is_active() ) { $feature->setup(); From 2e14c94dd33c610d225e895c7275f72b24f26a01 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 6 Jan 2017 14:00:03 -0500 Subject: [PATCH 103/159] Filter post type values when no post type is supplied --- classes/class-ep-wp-query-integration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/class-ep-wp-query-integration.php b/classes/class-ep-wp-query-integration.php index 2fcb372d22..e351475d6f 100644 --- a/classes/class-ep-wp-query-integration.php +++ b/classes/class-ep-wp-query-integration.php @@ -224,7 +224,7 @@ public function filter_posts_request( $request, $query ) { if ( empty( $query_vars['s'] ) ) { $query_vars['post_type'] = 'post'; } else { - $query_vars['post_type'] = get_post_types( array( 'exclude_from_search' => false ) ); + $query_vars['post_type'] = array_values( get_post_types( array( 'exclude_from_search' => false ) ) ); } } From 7cd3014ee1b1c6d70f75911e3ae2e67fde4a27b1 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 9 Jan 2017 12:04:08 -0500 Subject: [PATCH 104/159] ElasticPress in network admin bar menu --- classes/class-ep-dashboard.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index e5686db6fc..5b009a5635 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -32,6 +32,7 @@ public function __construct() { } public function setup() { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { // Must be network admin in multisite. add_action( 'network_admin_menu', array( $this, 'action_admin_menu' ) ); + add_action( 'admin_bar_menu', array( $this, 'action_network_admin_bar_menu' ), 50 ); } else { add_action( 'admin_menu', array( $this, 'action_admin_menu' ) ); } @@ -50,6 +51,21 @@ public function setup() { add_filter( 'network_admin_plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 2 ); } + /** + * Show ElasticPress in network admin menu bar + * + * @param object $admin_bar + * @since 2.2 + */ + public function action_network_admin_bar_menu( $admin_bar ) { + $admin_bar->add_menu( array( + 'id' => 'network-admin-elasticpress', + 'parent' => 'network-admin', + 'title' => 'ElasticPress', + 'href' => esc_url( network_admin_url( 'admin.php?page=elasticpress' ) ), + ) ); + } + /** * Output dashboard link in plugin actions * From 432b9e7cef0c88d3dd1afff5572ad6157d07faf4 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 11 Jan 2017 23:09:37 -0500 Subject: [PATCH 105/159] Dont erase settings when feature is activated --- classes/class-ep-features.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index afaf021b2a..40055082ba 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -81,7 +81,9 @@ public function activate_feature( $slug, $active = true ) { $feature = $this->registered_features[ $slug ]; - $feature_settings[ $slug ] = ( ! empty( $feature->default_settings ) ) ? $feature->default_settings : array(); + $current_feature_settings = ( ! empty( $feature_settings[ $slug ] ) ) ? $feature_settings[ $slug ] : array(); + + $feature_settings[ $slug ] = wp_parse_args( $current_feature_settings, $feature->default_settings ); $feature_settings[ $slug ]['active'] = (bool) $active; if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { From 0fb47c2bd7f4705e6a7b5e005eec03533b93abef Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 12 Jan 2017 00:08:52 -0500 Subject: [PATCH 106/159] Call feature activation hook on dashboard feature enable --- classes/class-ep-dashboard.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 5b009a5635..c8c59cdc9c 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -634,6 +634,8 @@ public function action_wp_ajax_ep_save_feature() { if ( ! empty( $feature->requires_install_reindex ) ) { $data['reindex'] = true; } + + $feature->post_activation(); } wp_send_json_success( $data ); From 56627baa6388668d5635e54944fc6b411d64c71e Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 12 Jan 2017 22:50:57 -0500 Subject: [PATCH 107/159] Make sure ep activate-feature doesnt throw warning --- bin/wp-cli.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index dbeed741d7..ad69269a47 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -78,7 +78,9 @@ public function activate_feature( $args, $assoc_args ) { WP_CLI::warning( printf( __( 'Feature is usable but there are warnings: %s', 'elasticpress' ), $status->message ) ); } - $active_features[ $feature->slug ] = wp_parse_args( $active_features[ $feature->slug ], $feature->default_settings ); + $feature_settings = ( ! empty( $active_features[ $feature->slug ] ) ) ? $active_features[ $feature->slug ] : array(); + + $active_features[ $feature->slug ] = wp_parse_args( $feature_settings, $feature->default_settings ); $active_features[ $feature->slug ]['active'] = true; $feature->post_activation(); From 4e69fbe6879eb17ca25e7dedac8bbc4e989259eb Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Wed, 18 Jan 2017 04:00:00 +0530 Subject: [PATCH 108/159] Fix multidimensional meta_query array #680 --- classes/class-ep-api.php | 528 ++++++++++++++++++++----------------- tests/test-single-site.php | 54 ++++ 2 files changed, 345 insertions(+), 237 deletions(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 4e96d0d8f1..8a1ed7c40a 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1242,248 +1242,14 @@ public function format_args( $args ) { } if ( ! empty( $meta_queries ) ) { - $meta_filter = array(); $relation = 'must'; if ( ! empty( $args['meta_query'] ) && ! empty( $args['meta_query']['relation'] ) && 'or' === strtolower( $args['meta_query']['relation'] ) ) { $relation = 'should'; } - - $meta_query_type_mapping = array( - 'numeric' => 'long', - 'binary' => 'raw', - 'char' => 'raw', - 'date' => 'date', - 'datetime' => 'datetime', - 'decimal' => 'double', - 'signed' => 'long', - 'time' => 'time', - 'unsigned' => 'long', - ); - - foreach( $meta_queries as $single_meta_query ) { - - /** - * There is a strange case where meta_query looks like this: - * array( - * "something" => array( - * array( - * 'key' => ... - * ... - * ) - * ) - * ) - * - * Somehow WordPress (WooCommerce) handles that case so we need to as well. - * - * @since 2.1 - */ - if ( is_array( $single_meta_query ) && empty( $single_meta_query['key'] ) ) { - reset( $single_meta_query ); - $first_key = key( $single_meta_query ); - - if ( is_array( $single_meta_query[$first_key] ) ) { - $single_meta_query = $single_meta_query[$first_key]; - } - } - - if ( ! empty( $single_meta_query['key'] ) ) { - - $terms_obj = false; - - $compare = '='; - if ( ! empty( $single_meta_query['compare'] ) ) { - $compare = strtolower( $single_meta_query['compare'] ); - } - - $type = null; - if ( ! empty( $single_meta_query['type'] ) ) { - $type = strtolower( $single_meta_query['type'] ); - } - - // Comparisons need to look at different paths - if ( in_array( $compare, array( 'exists', 'not exists' ) ) ) { - $meta_key_path = 'meta.' . $single_meta_query['key']; - } elseif ( in_array( $compare, array( '=', '!=' ) ) && ! $type ) { - $meta_key_path = 'meta.' . $single_meta_query['key'] . '.raw'; - } elseif ( 'like' === $compare ) { - $meta_key_path = 'meta.' . $single_meta_query['key'] . '.value'; - } elseif ( $type && isset( $meta_query_type_mapping[ $type ] ) ) { - // Map specific meta field types to different Elasticsearch core types - $meta_key_path = 'meta.' . $single_meta_query['key'] . '.' . $meta_query_type_mapping[ $type ]; - } elseif ( in_array( $compare, array( '>=', '<=', '>', '<', 'between' ) ) ) { - $meta_key_path = 'meta.' . $single_meta_query['key'] . '.double'; - } else { - $meta_key_path = 'meta.' . $single_meta_query['key'] . '.raw'; - } - - switch ( $compare ) { - case 'not in': - case '!=': - if ( isset( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'bool' => array( - 'must_not' => array( - array( - 'terms' => array( - $meta_key_path => (array) $single_meta_query['value'], - ), - ), - ), - ), - ); - } - - break; - case 'exists': - $terms_obj = array( - 'exists' => array( - 'field' => $meta_key_path, - ), - ); - - break; - case 'not exists': - $terms_obj = array( - 'bool' => array( - 'must_not' => array( - array( - 'exists' => array( - 'field' => $meta_key_path, - ), - ), - ), - ), - ); - - break; - case '>=': - if ( isset( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'bool' => array( - 'must' => array( - array( - 'range' => array( - $meta_key_path => array( - "gte" => $single_meta_query['value'], - ), - ), - ), - ), - ), - ); - } - - break; - case 'between': - if ( isset( $single_meta_query['value'] ) && is_array( $single_meta_query['value'] ) && 2 === count( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'bool' => array( - 'must' => array( - array( - 'range' => array( - $meta_key_path => array( - "gte" => $single_meta_query['value'][0], - ), - ), - ), - array( - 'range' => array( - $meta_key_path => array( - "lte" => $single_meta_query['value'][1], - ), - ), - ), - ), - ), - ); - } - - break; - case '<=': - if ( isset( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'bool' => array( - 'must' => array( - array( - 'range' => array( - $meta_key_path => array( - 'lte' => $single_meta_query['value'], - ), - ), - ), - ), - ), - ); - } - - break; - case '>': - if ( isset( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'bool' => array( - 'must' => array( - array( - 'range' => array( - $meta_key_path => array( - 'gt' => $single_meta_query['value'], - ), - ), - ), - ), - ), - ); - } - - break; - case '<': - if ( isset( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'bool' => array( - 'must' => array( - array( - 'range' => array( - $meta_key_path => array( - 'lt' => $single_meta_query['value'], - ), - ), - ), - ), - ), - ); - } - - break; - case 'like': - if ( isset( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'query' => array( - 'match' => array( - $meta_key_path => $single_meta_query['value'], - ) - ), - ); - } - break; - case '=': - default: - if ( isset( $single_meta_query['value'] ) ) { - $terms_obj = array( - 'terms' => array( - $meta_key_path => (array) $single_meta_query['value'], - ), - ); - } - - break; - } - - // Add the meta query filter - if ( false !== $terms_obj ) { - $meta_filter[] = $terms_obj; - } - } - } + + // get meta query filter + $meta_filter = $this->build_meta_query( $meta_queries ); if ( ! empty( $meta_filter ) ) { $filter['bool']['must'][]['bool'][$relation] = $meta_filter; @@ -1728,6 +1494,294 @@ public function format_args( $args ) { } return apply_filters( 'ep_formatted_args', $formatted_args, $args ); } + + /** + * Build Elasticsearch filter query for WP meta_query + * + * @since 2.2 + * + * @param $meta_queries + * + * @return array + */ + public function build_meta_query( $meta_queries ){ + $meta_filter = array(); + + if ( ! empty( $meta_queries ) ) { + $meta_filter = array(); + + $meta_query_type_mapping = array( + 'numeric' => 'long', + 'binary' => 'raw', + 'char' => 'raw', + 'date' => 'date', + 'datetime' => 'datetime', + 'decimal' => 'double', + 'signed' => 'long', + 'time' => 'time', + 'unsigned' => 'long', + ); + + foreach( $meta_queries as $single_meta_query ) { + + /** + * There is a strange case where meta_query looks like this: + * array( + * "something" => array( + * array( + * 'key' => ... + * ... + * ) + * ) + * ) + * + * Somehow WordPress (WooCommerce) handles that case so we need to as well. + * + * @since 2.1 + */ + if ( is_array( $single_meta_query ) && empty( $single_meta_query['key'] ) ) { + reset( $single_meta_query ); + $first_key = key( $single_meta_query ); + + if ( is_array( $single_meta_query[$first_key] ) ) { + $single_meta_query = $single_meta_query[$first_key]; + } + } + + if ( ! empty( $single_meta_query['key'] ) ) { + + $terms_obj = false; + + $compare = '='; + if ( ! empty( $single_meta_query['compare'] ) ) { + $compare = strtolower( $single_meta_query['compare'] ); + } + + $type = null; + if ( ! empty( $single_meta_query['type'] ) ) { + $type = strtolower( $single_meta_query['type'] ); + } + + // Comparisons need to look at different paths + if ( in_array( $compare, array( 'exists', 'not exists' ) ) ) { + $meta_key_path = 'meta.' . $single_meta_query['key']; + } elseif ( in_array( $compare, array( '=', '!=' ) ) && ! $type ) { + $meta_key_path = 'meta.' . $single_meta_query['key'] . '.raw'; + } elseif ( 'like' === $compare ) { + $meta_key_path = 'meta.' . $single_meta_query['key'] . '.value'; + } elseif ( $type && isset( $meta_query_type_mapping[ $type ] ) ) { + // Map specific meta field types to different Elasticsearch core types + $meta_key_path = 'meta.' . $single_meta_query['key'] . '.' . $meta_query_type_mapping[ $type ]; + } elseif ( in_array( $compare, array( '>=', '<=', '>', '<', 'between' ) ) ) { + $meta_key_path = 'meta.' . $single_meta_query['key'] . '.double'; + } else { + $meta_key_path = 'meta.' . $single_meta_query['key'] . '.raw'; + } + + switch ( $compare ) { + case 'not in': + case '!=': + if ( isset( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'bool' => array( + 'must_not' => array( + array( + 'terms' => array( + $meta_key_path => (array) $single_meta_query['value'], + ), + ), + ), + ), + ); + } + + break; + case 'exists': + $terms_obj = array( + 'exists' => array( + 'field' => $meta_key_path, + ), + ); + + break; + case 'not exists': + $terms_obj = array( + 'bool' => array( + 'must_not' => array( + array( + 'exists' => array( + 'field' => $meta_key_path, + ), + ), + ), + ), + ); + + break; + case '>=': + if ( isset( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'bool' => array( + 'must' => array( + array( + 'range' => array( + $meta_key_path => array( + "gte" => $single_meta_query['value'], + ), + ), + ), + ), + ), + ); + } + + break; + case 'between': + if ( isset( $single_meta_query['value'] ) && is_array( $single_meta_query['value'] ) && 2 === count( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'bool' => array( + 'must' => array( + array( + 'range' => array( + $meta_key_path => array( + "gte" => $single_meta_query['value'][0], + ), + ), + ), + array( + 'range' => array( + $meta_key_path => array( + "lte" => $single_meta_query['value'][1], + ), + ), + ), + ), + ), + ); + } + + break; + case '<=': + if ( isset( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'bool' => array( + 'must' => array( + array( + 'range' => array( + $meta_key_path => array( + 'lte' => $single_meta_query['value'], + ), + ), + ), + ), + ), + ); + } + + break; + case '>': + if ( isset( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'bool' => array( + 'must' => array( + array( + 'range' => array( + $meta_key_path => array( + 'gt' => $single_meta_query['value'], + ), + ), + ), + ), + ), + ); + } + + break; + case '<': + if ( isset( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'bool' => array( + 'must' => array( + array( + 'range' => array( + $meta_key_path => array( + 'lt' => $single_meta_query['value'], + ), + ), + ), + ), + ), + ); + } + + break; + case 'like': + if ( isset( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'query' => array( + 'match' => array( + $meta_key_path => $single_meta_query['value'], + ) + ), + ); + } + break; + case '=': + default: + if ( isset( $single_meta_query['value'] ) ) { + $terms_obj = array( + 'terms' => array( + $meta_key_path => (array) $single_meta_query['value'], + ), + ); + } + + break; + } + + // Add the meta query filter + if ( false !== $terms_obj ) { + $meta_filter[] = $terms_obj; + } + } elseif ( is_array( $single_meta_query ) && isset( $single_meta_query[0] ) && is_array( $single_meta_query[0] ) ) { + /* + * Handle multidimensional array. Something like: + * + * 'meta_query' => array( + * 'relation' => 'AND', + * array( + * 'key' => 'meta_key_1', + * 'value' => '1', + * ), + * array( + * 'relation' => 'OR', + * array( + * 'key' => 'meta_key_2', + * 'value' => '2', + * ), + * array( + * 'key' => 'meta_key_3', + * 'value' => '4', + * ), + * ), + * ), + */ + $relation = 'must'; + if ( ! empty( $single_meta_query['relation'] ) && 'or' === strtolower( $single_meta_query['relation'] ) ) { + $relation = 'should'; + } + + $meta_filter[] = array( + 'bool' => array( + $relation => $this->build_meta_query( $single_meta_query ), + ), + ); + } + } + } + + return $meta_filter; + } /** * Wrapper function for get_sites - allows us to have one central place for the `ep_indexable_sites` filter diff --git a/tests/test-single-site.php b/tests/test-single-site.php index a3845a1b76..9d08932309 100644 --- a/tests/test-single-site.php +++ b/tests/test-single-site.php @@ -2327,6 +2327,60 @@ public function testMetaQueryLike() { $this->assertEquals( 3, $query->post_count ); $this->assertEquals( 3, $query->found_posts ); } + + public function testMetaQueryMultipleArray() { + ep_create_and_sync_post( array( 'post_content' => 'findme' ), array( 'meta_key_1' => '1' ) ); + ep_create_and_sync_post( array( 'post_content' => 'findme' ), array( 'meta_key_1' => '1' ) ); + ep_create_and_sync_post( array( 'post_content' => 'findme' ), array( 'meta_key_1' => '1', 'meta_key_2' => '4' ) ); + ep_create_and_sync_post( array( 'post_content' => 'findme' ), array( 'meta_key_1' => '1', 'meta_key_2' => '0' ) ); + ep_create_and_sync_post( array( 'post_content' => 'findme' ), array( 'meta_key_1' => '1', 'meta_key_3' => '4' ) ); + + ep_refresh_index(); + + $args = array( + 's' => 'findme', + 'meta_query' => array( + array( + 'key' => 'meta_key_2', + 'value' => '0', + 'compare' => '>=', + ) + ), + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 2, $query->post_count ); + $this->assertEquals( 2, $query->found_posts ); + + $args = array( + 's' => 'findme', + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'meta_key_1', + 'value' => '1', + ), + array( + 'relation' => 'OR', + array( + 'key' => 'meta_key_2', + 'value' => '2', + 'compare' => '>=', + ), + array( + 'key' => 'meta_key_3', + 'value' => '4', + ), + ), + ), + ); + + $query = new WP_Query( $args ); + + $this->assertEquals( 2, $query->post_count ); + $this->assertEquals( 2, $query->found_posts ); + } /** * Test exclude_from_search post type flag From a5cac51cae5a6dc42a9b9e200d0852274ae08d1f Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 18 Jan 2017 09:38:15 -0500 Subject: [PATCH 109/159] Dont dereference query argument. Closes #682 --- classes/class-ep-wp-query-integration.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/class-ep-wp-query-integration.php b/classes/class-ep-wp-query-integration.php index e351475d6f..4c7805acc6 100644 --- a/classes/class-ep-wp-query-integration.php +++ b/classes/class-ep-wp-query-integration.php @@ -159,10 +159,10 @@ public function action_loop_end( $query ) { * Filter the posts array to contain ES query results in EP_Post form. Pull previously queried posts. * * @param array $posts - * @param object &$query + * @param object $query * @return array */ - public function filter_the_posts( $posts, &$query ) { + public function filter_the_posts( $posts, $query ) { if ( ! ep_elasticpress_enabled( $query ) || apply_filters( 'ep_skip_query_integration', false, $query ) || ! isset( $this->posts_by_query[spl_object_hash( $query )] ) ) { return $posts; } From 5debe1075615124fa2d4b10b612d6492dfa0004b Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 18 Jan 2017 12:40:52 -0500 Subject: [PATCH 110/159] Fix php 5.5 error --- bin/wp-cli.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index ad69269a47..2d928ab10e 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -42,7 +42,7 @@ class ElasticPress_CLI_Command extends WP_CLI_Command { * * @since 2.1.1 */ - private $transient_expiration = 15 * MINUTE_IN_SECONDS; + private $transient_expiration = 900; // 15 min /** * Activate a feature. From ff3011e85c8a19c1789ab1dade860cf915290465 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Thu, 19 Jan 2017 16:45:04 +0530 Subject: [PATCH 111/159] define $meta_filter variable only one time #680 --- classes/class-ep-api.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 8a1ed7c40a..2ebf8c42a1 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1508,7 +1508,6 @@ public function build_meta_query( $meta_queries ){ $meta_filter = array(); if ( ! empty( $meta_queries ) ) { - $meta_filter = array(); $meta_query_type_mapping = array( 'numeric' => 'long', From 7cfb20b9e059ed83d8205cfa2b2a8d4f118bd60e Mon Sep 17 00:00:00 2001 From: "ryan.veitch" Date: Thu, 19 Jan 2017 11:37:18 -0600 Subject: [PATCH 112/159] Adds 'number' param to satisfy WP v4.6+ In Wordpress v4.6 when get_sites() was introduced to replace `wp_get_sites()` the 'limit' argument parameter changed to `number`. Without `number`, multisite installations will revert to only returning the first 100 sites. This update adds the `number` param to the `ep_indexable_sites_args()` filter to resolve this issue while maintaining backwards compatibility with `limit`. --- classes/class-ep-api.php | 1 + 1 file changed, 1 insertion(+) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 2ebf8c42a1..42423079a1 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -1792,6 +1792,7 @@ public function build_meta_query( $meta_queries ){ public function get_sites( $limit = 0 ) { $args = apply_filters( 'ep_indexable_sites_args', array( 'limit' => $limit, + 'number' => $limit, ) ); if ( function_exists( 'get_sites' ) ) { From 2895901fa131988d38c1a80c181f33b530443491 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 19 Jan 2017 15:02:54 -0500 Subject: [PATCH 113/159] Only integrate with admin if admin feature is on --- features/admin/admin.php | 24 ++++++++++++++--- features/woocommerce/woocommerce.php | 39 ++++++++++++---------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/features/admin/admin.php b/features/admin/admin.php index e0cefeabf6..2dfb390973 100644 --- a/features/admin/admin.php +++ b/features/admin/admin.php @@ -14,12 +14,30 @@ */ function ep_admin_setup() { add_filter( 'ep_indexable_post_status', 'ep_admin_get_statuses' ); + add_filter( 'ep_indexable_post_types', 'ep_admin_post_types', 10, 1 ); + if ( is_admin() ) { add_filter( 'ep_admin_wp_query_integration', '__return_true' ); add_action( 'pre_get_posts', 'ep_admin_integrate' ); } } +/** + * Index all post types + * + * @param array $post_types Existing post types. + * @since 2.2 + * @return array + */ +function ep_admin_post_types( $post_types ) { + $all_post_types = get_post_types(); + + // We don't want to deal with nav menus + unset( $all_post_types['nav_menu_item'] ); + + return array_unique( array_merge( $post_types, $all_post_types ) ); +} + /** * Integrate EP into proper queries * @@ -78,7 +96,7 @@ function ep_admin_integrate( $query ) { */ function ep_admin_feature_box_summary() { ?> -

+

-

+

-

+

'shop_order', - 'shop_coupon' => 'shop_coupon', - 'shop_order_refund' => 'shop_order_refund', - 'product_variation' => 'product_variation', - 'product' => 'product', - ) ) ); -} - /** * Index Woocommerce meta * @@ -185,6 +168,20 @@ function ep_wc_translate_args( $query ) { return; } + $admin_integration = apply_filters( 'ep_admin_wp_query_integration', false ); + + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + if ( ! apply_filters( 'ep_ajax_wp_query_integration', false ) ) { + return; + } else { + $admin_integration = true; + } + } + + if ( is_admin() && ! $admin_integration ) { + return; + } + $product_name = $query->get( 'product', false ); $post_parent = $query->get( 'post_parent', false ); @@ -507,7 +504,6 @@ function ep_wc_setup() { if( function_exists( 'WC' ) ) { add_filter( 'ep_sync_insert_permissions_bypass', 'ep_wc_bypass_order_permissions_check', 10, 2 ); add_filter( 'ep_elasticpress_enabled', 'ep_wc_blacklist_coupons', 10 ,2 ); - add_filter( 'ep_indexable_post_types', 'ep_wc_post_types', 10, 1 ); add_filter( 'ep_prepare_meta_allowed_protected_keys', 'ep_wc_whitelist_meta_keys', 10, 2 ); add_filter( 'woocommerce_shop_order_search_fields', 'ep_wc_shop_order_search_fields', 9999 ); add_filter( 'woocommerce_layered_nav_query_post_ids', 'ep_wc_convert_post_object_to_id', 10, 4 ); @@ -515,9 +511,6 @@ function ep_wc_setup() { add_filter( 'ep_sync_taxonomies', 'ep_wc_whitelist_taxonomies', 10, 2 ); add_filter( 'ep_post_sync_args_post_prepare_meta', 'ep_wc_remove_legacy_meta', 10, 2 ); add_action( 'pre_get_posts', 'ep_wc_translate_args', 11, 1 ); - add_filter( 'ep_admin_wp_query_integration', '__return_true' ); - add_filter( 'ep_indexable_post_status', 'ep_admin_get_statuses' ); - add_filter( 'ep_elasticpress_enabled', 'ep_integrate_search_queries', 10, 2 ); } } @@ -539,9 +532,9 @@ function ep_wc_feature_box_summary() { */ function ep_wc_feature_box_long() { ?> -

+

-

+

Date: Thu, 19 Jan 2017 15:05:34 -0500 Subject: [PATCH 114/159] Update readme for admin feature --- README.md | 2 +- readme.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 77cad94cf2..0005d21d09 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Beef up your search to be more accurate, search tags, categories, and other taxo ### WooCommerce -Allow customers to filter through products faster and improve product search relevancy. Enable editors to find orders and products more effectively in the admin. This feature will increase your sales bottom line and reduce administrative costs. +Allow customers to filter through products faster and improve product search relevancy. This feature will increase your sales bottom line and reduce administrative costs. ### Related Posts diff --git a/readme.txt b/readme.txt index 7a155a4a3c..41978f509f 100644 --- a/readme.txt +++ b/readme.txt @@ -45,6 +45,7 @@ Version 2.2 rethinks the module process to make ElasticPress a more complete que * Admin warning if current Elasticsearch version is not between the min/max supported version. Version 2.2 supports versions 1.3 - 5.1. * Auto-reindex on versions requiring reindex. * User friendly admin notifications for ElasticPress not set up, first sync needed, and feature auto activation. +* Admin feature applies to all features. This means if Admin isn't active, search or WooCommerce integration won't happen in the admin. ### Bug Fixes From a729bc328ae6a90d5dbdab52fa398559fe2ae530 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Thu, 19 Jan 2017 15:35:00 -0500 Subject: [PATCH 115/159] Properly enable admin feature to test shop orders; test non-admin features on front --- tests/features/test-woocommerce.php | 6 ++++-- tests/test-multisite.php | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/features/test-woocommerce.php b/tests/features/test-woocommerce.php index 1e91146068..9cd57807f7 100644 --- a/tests/features/test-woocommerce.php +++ b/tests/features/test-woocommerce.php @@ -77,7 +77,7 @@ public function testProductsPostTypeQueryOn() { * @since 2.1 * @group woocommerce */ - public function testProductsPostTypeQueryShopOrder() { + /*public function testProductsPostTypeQueryShopOrder() { ep_activate_feature( 'woocommerce' ); EP_Features::factory()->setup_features(); @@ -97,7 +97,7 @@ public function testProductsPostTypeQueryShopOrder() { $this->assertTrue( ! empty( $this->fired_actions['ep_wp_query_search'] ) ); $this->assertEquals( 1, $query->post_count ); $this->assertEquals( 1, $query->found_posts ); - } + }*/ /** * Test products post type query does get integrated when querying WC product_cat taxonomy @@ -106,6 +106,7 @@ public function testProductsPostTypeQueryShopOrder() { * @group woocommerce */ public function testProductsPostTypeQueryProductCatTax() { + ep_activate_feature( 'admin' ); ep_activate_feature( 'woocommerce' ); EP_Features::factory()->setup_features(); @@ -137,6 +138,7 @@ public function testProductsPostTypeQueryProductCatTax() { * @group woocommerce */ public function testSearchOnShopOrderAdmin() { + ep_activate_feature( 'admin' ); ep_activate_feature( 'woocommerce' ); EP_Features::factory()->setup_features(); diff --git a/tests/test-multisite.php b/tests/test-multisite.php index 62e4d71cc2..f7867f4c2a 100644 --- a/tests/test-multisite.php +++ b/tests/test-multisite.php @@ -40,6 +40,8 @@ public function setUp() { $this->setup_test_post_type(); + set_current_screen( 'front' ); + /** * Most of our search test are bundled into core tests for legacy reasons */ From 00c3d9cf80fd8173208e160642d5e8465ec42fac Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 20 Jan 2017 09:23:54 -0500 Subject: [PATCH 116/159] Add elasticpress loaded hook --- elasticpress.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/elasticpress.php b/elasticpress.php index d0d0f6d65c..3094d3c29e 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -131,3 +131,5 @@ function ep_setup_misc() { } } add_action( 'plugins_loaded', 'ep_setup_misc' ); + +do_action( 'elasticpress_loaded' ); From b1a34190acbf66e0a4ff38bcc11141b34b7bc574 Mon Sep 17 00:00:00 2001 From: Scott Kingsley Clark Date: Thu, 26 Jan 2017 04:31:37 -0600 Subject: [PATCH 117/159] Stop the insanity should be private --- bin/wp-cli.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index 2d928ab10e..b515556818 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -769,7 +769,7 @@ public function stats() { /** * Resets some values to reduce memory footprint. */ - public function stop_the_insanity() { + private function stop_the_insanity() { global $wpdb, $wp_object_cache, $wp_actions, $wp_filter; $wpdb->queries = array(); From 43ffbb16c1033cffdbbcacb9091db9c27a096c2e Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 30 Jan 2017 14:28:06 -0500 Subject: [PATCH 118/159] Make sure dashboard sync notifications are cleared away in wp cli sync --- bin/wp-cli.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index 2d928ab10e..fb45d82c80 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -372,6 +372,17 @@ public function index( $args, $assoc_args ) { timer_start(); + // This clears away dashboard notifications + if ( isset( $assoc_args['network-wide'] ) && is_multisite() ) { + update_site_option( 'ep_last_sync', time() ); + delete_site_option( 'ep_need_upgrade_sync' ); + delete_site_option( 'ep_feature_auto_activated_sync' ); + } else { + update_option( 'ep_last_sync', time() ); + delete_option( 'ep_need_upgrade_sync' ); + delete_option( 'ep_feature_auto_activated_sync' ); + } + // Run setup if flag was passed if ( isset( $assoc_args['setup'] ) && true === $assoc_args['setup'] ) { From 5fb32a05ab319d62a8b05003d95350a4a03dff94 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 30 Jan 2017 14:59:19 -0500 Subject: [PATCH 119/159] Properly format bulk cli error messages --- bin/wp-cli.php | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index fb45d82c80..86cbc982ad 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -697,6 +697,27 @@ private function bulk_index( $show_bulk_errors = false ) { } } + /** + * Formatting bulk error message recursively + * + * @param array $message_array + * @since 2.2 + * @return string + */ + private function format_bulk_error_message( $message_array ) { + $message = ''; + + foreach ( $message_array as $key => $value ) { + if ( is_array( $value ) ) { + $message .= $this->format_bulk_error_message( $value ); + } else { + $message .= "$key: $value" . PHP_EOL; + } + } + + return $message; + } + /** * Send any bulk indexing errors * @@ -705,12 +726,14 @@ private function bulk_index( $show_bulk_errors = false ) { private function send_bulk_errors() { if ( ! empty( $this->failed_posts ) ) { $error_text = __( "The following posts failed to index:\r\n\r\n", 'elasticpress' ); + foreach ( $this->failed_posts as $failed ) { $failed_post = get_post( $failed ); if ( $failed_post ) { $error_text .= "- {$failed}: " . $failed_post->post_title . "\r\n"; - if ( array_key_exists( $failed, $this->failed_posts_message ) ) { - $error_text .= "\t" . $this->failed_posts_message[ $failed ] . PHP_EOL; + + if ( ! empty( $this->failed_posts_message[ $failed ] ) ) { + $error_text .= $this->format_bulk_error_message( $this->failed_posts_message[ $failed ] ) . PHP_EOL; } } } From 171e32d51e214c7fd610809a54372eef3ddc7796 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 30 Jan 2017 15:30:22 -0500 Subject: [PATCH 120/159] Better admin feature description --- README.md | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0005d21d09..a8f10e5aa3 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Help users easily find related content by adding related posts to the end of eac ### Admin -Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. You'll need to make sure your Elasticsearch instance is properly secured. +Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. Enabling this feature will allow other ElasticPress features to work within the admin (i.e. WooCommerce and Search). You'll need to make sure your Elasticsearch instance is properly secured. ## `WP_Query` and the ElasticPress Query Integration diff --git a/readme.txt b/readme.txt index 41978f509f..11f17978eb 100644 --- a/readme.txt +++ b/readme.txt @@ -22,7 +22,7 @@ __WooCommerce__: Allow customers to filter through products faster and improve p __Related Posts__: Help users easily find related content by adding related posts to the end of each post. -__Admin__: Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. You'll need to make sure your Elasticsearch instance is properly secured. +__Admin__: Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. Enabling this feature will allow other ElasticPress features to work within the admin (i.e. WooCommerce and Search). You'll need to make sure your Elasticsearch instance is properly secured. Please refer to [Github](https://github.com/10up/ElasticPress) for detailed usage instructions and documentation. From b9d8fd79607d371a494e4e052be23bcc6b2ed2b1 Mon Sep 17 00:00:00 2001 From: Ritesh-patel Date: Wed, 1 Feb 2017 18:15:14 +0530 Subject: [PATCH 121/159] Fix number of posts in related posts widget form --- features/related-posts/widget.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/related-posts/widget.php b/features/related-posts/widget.php index 003deeef24..17f89ac2a8 100644 --- a/features/related-posts/widget.php +++ b/features/related-posts/widget.php @@ -81,7 +81,7 @@ public function widget( $args, $instance ) { */ public function form( $instance ) { $title = ( isset( $instance['title'] ) ) ? $instance['title'] : ''; - $num_posts = ( isset( $instance['form_id'] ) ) ? $instance['form_id'] : 5; + $num_posts = ( isset( $instance['num_posts'] ) ) ? $instance['num_posts'] : 5; ?>

-

+

'Admin', - 'setup_cb' => 'ep_admin_setup', - 'requirements_status_cb' => 'ep_admin_requirements_status', - 'feature_box_summary_cb' => 'ep_admin_feature_box_summary', - 'feature_box_long_cb' => 'ep_admin_feature_box_long', +ep_register_feature( 'protected-content', array( + 'title' => esc_html__( 'Protected Content', 'elasticpress' ), + 'setup_cb' => 'ep_pc_setup', + 'requirements_status_cb' => 'ep_pc_requirements_status', + 'feature_box_summary_cb' => 'ep_pc_feature_box_summary', + 'feature_box_long_cb' => 'ep_pc_feature_box_long', 'requires_install_reindex' => true, ) ); diff --git a/readme.txt b/readme.txt index 00b5ffdd3b..77fd3df849 100644 --- a/readme.txt +++ b/readme.txt @@ -22,7 +22,7 @@ __WooCommerce__: Allow customers to filter through products faster and improve p __Related Posts__: Help users easily find related content by adding related posts to the end of each post. -__Admin__: Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. Enabling this feature will allow other ElasticPress features to work within the admin (i.e. WooCommerce and Search). You'll need to make sure your Elasticsearch instance is properly secured. +__Protected Content__: Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. Enabling this feature will allow other ElasticPress features to work within the admin (i.e. WooCommerce and Search). You'll need to make sure your Elasticsearch instance is properly secured. Please refer to [Github](https://github.com/10up/ElasticPress) for detailed usage instructions and documentation. @@ -42,6 +42,7 @@ Version 2.2 rethinks the module process to make ElasticPress a more complete que * (Breaking change) Module registration API changed. See `register_module` in `classes/class-ep-modules.php`. * (Breaking change) Related posts are now in a widget instead of automatically being appending to content. +* (Breaking change) Admin module renamed to Protected Content. * Admin warning if current Elasticsearch version is not between the min/max supported version. Version 2.2 supports versions 1.3 - 5.1. * Auto-reindex on versions requiring reindex. * User friendly admin notifications for ElasticPress not set up, first sync needed, and feature auto activation. diff --git a/tests/features/test-admin.php b/tests/features/test-protected-content.php similarity index 92% rename from tests/features/test-admin.php rename to tests/features/test-protected-content.php index 23b3793c63..d1f8c7334c 100644 --- a/tests/features/test-admin.php +++ b/tests/features/test-protected-content.php @@ -1,12 +1,12 @@ setup_features(); ep_create_and_sync_post(); @@ -106,12 +106,12 @@ public function testAdminOn() { * Test main query on is integrated on drafts with feature on * * @since 2.1 - * @group admin + * @group protected-content */ public function testAdminOnDraft() { set_current_screen( 'edit.php' ); - ep_activate_feature( 'admin' ); + ep_activate_feature( 'protected-content' ); EP_Features::factory()->setup_features(); ep_create_and_sync_post(); @@ -142,12 +142,12 @@ public function testAdminOnDraft() { * Check post updated to draft shows up * * @since 2.1 - * @group admin + * @group protected-content */ public function testAdminOnDraftUpdated() { set_current_screen( 'edit.php' ); - ep_activate_feature( 'admin' ); + ep_activate_feature( 'protected-content' ); EP_Features::factory()->setup_features(); ep_create_and_sync_post(); From c7d5d0af44d2d4a6d8cf29c8395b174ee010002c Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Wed, 15 Feb 2017 20:33:51 -0500 Subject: [PATCH 142/159] Minimum ES version changed to 1.7; maximum changed to 5.2; readme updates --- README.md | 2 +- elasticpress.php | 4 ++-- readme.txt | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3b69530a5b..a53ad43c45 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ ElasticPress integrates with the [WP_Query](http://codex.wordpress.org/Class_Ref ## Requirements -* [Elasticsearch](https://www.elastic.co) 1.3+ +* [Elasticsearch](https://www.elastic.co) 1.7 - 5.2 (2.0+ highly recommended) * [WordPress](http://wordpress.org) 3.7.1+ ## Installation diff --git a/elasticpress.php b/elasticpress.php index 119a31787e..14e22564fd 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -33,8 +33,8 @@ * * @since 2.2 */ -define( 'EP_ES_VERSION_MAX', '5.1' ); -define( 'EP_ES_VERSION_MIN', '1.3' ); +define( 'EP_ES_VERSION_MAX', '5.2' ); +define( 'EP_ES_VERSION_MIN', '1.7' ); require_once( 'classes/class-ep-config.php' ); require_once( 'classes/class-ep-api.php' ); diff --git a/readme.txt b/readme.txt index 04f1b6ed21..b3c502b00f 100644 --- a/readme.txt +++ b/readme.txt @@ -36,7 +36,9 @@ Please refer to [Github](https://github.com/10up/ElasticPress) for detailed usag = 2.2 (Requires re-index) = -Version 2.2 rethinks the module process to make ElasticPress a more complete query engine solution. Modules are now auto-on and really just features. Why would anyone want to not use amazing functionality that improves speed and relevancy on their website? Features (previously modules) can of course be overriden and disabled. Also, features that don't have their minimum requirements met are still auto-disabled. +Version 2.2 rethinks the module process to make ElasticPress a more complete query engine solution. Modules are now auto-on and really just features. Why would anyone want to not use amazing functionality that improves speed and relevancy on their website? Features (previously modules) can of course be overriden and disabled. Features that don't have their minimum requirements met, such as a plugin dependency, are auto-disabled. + +We've bumped the minimum Elasticsearch version to 1.7 (although we strongly recommend 2+). The maximum tested version of Elasticsearch is version 5.2. If you are running Elasticsearch outside this version range, you will see a warning in the dashboard. ### Enhancements @@ -46,7 +48,7 @@ Version 2.2 rethinks the module process to make ElasticPress a more complete que * Admin warning if current Elasticsearch version is not between the min/max supported version. Version 2.2 supports versions 1.3 - 5.1. * Auto-reindex on versions requiring reindex. * User friendly admin notifications for ElasticPress not set up, first sync needed, and feature auto activation. -* Admin feature applies to all features. This means if Admin isn't active, search or WooCommerce integration won't happen in the admin. +* Protected Content feature applies to all features. This means if Protected Content isn't active, search or WooCommerce integration won't happen in the admin. * Add support for post_mime_type. Props (Ritesh-patel)[https://github.com/Ritesh-patel] ### Bug Fixes From 7cc4c0765ecfa7e4bf622026c26b0466d8085eef Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 19 Feb 2017 20:54:35 -0500 Subject: [PATCH 143/159] Dont EP integrate on admin page table --- features/protected-content/protected-content.php | 1 - 1 file changed, 1 deletion(-) diff --git a/features/protected-content/protected-content.php b/features/protected-content/protected-content.php index 8110ea5c67..79ce1e5d62 100644 --- a/features/protected-content/protected-content.php +++ b/features/protected-content/protected-content.php @@ -63,7 +63,6 @@ function ep_pc_integrate( $query ) { */ $post_types = array( 'post' => 'post', - 'page' => 'page', ); // Backwards compat From 1417fb14b855c933255c9a9137778956c78114c8 Mon Sep 17 00:00:00 2001 From: Ivan Kristianto Date: Mon, 20 Feb 2017 16:54:14 +0700 Subject: [PATCH 144/159] fix error indexing with pagination fix #714 --- bin/wp-cli.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index c1bd27fb65..6dd48e0df1 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -44,6 +44,13 @@ class ElasticPress_CLI_Command extends WP_CLI_Command { */ private $transient_expiration = 900; // 15 min + /** + * Holds temporary wp_actions when indexing with pagination + * + * @since 2.2 + */ + private $temporary_wp_actions = array(); + /** * Activate a feature. * @@ -335,6 +342,8 @@ private function _create_network_alias() { * @param array $assoc_args */ public function index( $args, $assoc_args ) { + global $wp_actions; + $this->_connect_check(); if ( ! empty( $assoc_args['posts-per-page'] ) ) { @@ -355,6 +364,9 @@ public function index( $args, $assoc_args ) { $total_indexed = 0; + //Hold original wp_actions + $this->temporary_wp_actions = $wp_actions; + /** * Prior to the index command invoking * Useful for deregistering filters/actions that occur during a query request @@ -837,7 +849,7 @@ private function stop_the_insanity() { } // Prevent wp_actions from growing out of control - $wp_actions = array(); + $wp_actions = $this->temporary_wp_actions; // WP_Query class adds filter get_term_metadata using its own instance // what prevents WP_Query class from being destructed by PHP gc. From 8eaea74fb37c1e3bbaad21b35259ee30d9b8f403 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 20 Feb 2017 23:23:51 -0500 Subject: [PATCH 145/159] Properly activate protected content in tests --- tests/features/test-protected-content.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/features/test-protected-content.php b/tests/features/test-protected-content.php index d1f8c7334c..9c9f1f82f6 100644 --- a/tests/features/test-protected-content.php +++ b/tests/features/test-protected-content.php @@ -185,7 +185,7 @@ public function testAdminOnDraftUpdated() { public function testAdminCategories() { set_current_screen( 'edit.php' ); - ep_activate_feature( 'admin' ); + ep_activate_feature( 'protected-content' ); EP_Features::factory()->setup_features(); $cat1 = wp_create_category( 'category one' ); From fa7fbbdc4447f0c657ad443ab75eae2c1179ac1c Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 20 Feb 2017 23:29:47 -0500 Subject: [PATCH 146/159] Redo behind the scenes update feature functionaliy; properly remove auto sync message when deactivating a feature; rename protected content feature slug --- bin/wp-cli.php | 29 +-------- classes/class-ep-dashboard.php | 35 +++-------- classes/class-ep-features.php | 62 ++++++++++++++----- .../protected-content/protected-content.php | 2 +- 4 files changed, 58 insertions(+), 70 deletions(-) diff --git a/bin/wp-cli.php b/bin/wp-cli.php index c1bd27fb65..f286dba994 100644 --- a/bin/wp-cli.php +++ b/bin/wp-cli.php @@ -60,12 +60,6 @@ public function activate_feature( $args, $assoc_args ) { WP_CLI::error( __( 'No feature with that slug is registered', 'elasticpress' ) ); } - if ( ! empty( $assoc_args['network-wide'] ) ) { - $active_features = get_site_option( 'ep_feature_settings', array() ); - } else { - $active_features = get_option( 'ep_feature_settings', array() ); - } - if ( $feature->is_active() ) { WP_CLI::error( __( 'This feature is already active', 'elasticpress' ) ); } @@ -78,23 +72,12 @@ public function activate_feature( $args, $assoc_args ) { WP_CLI::warning( printf( __( 'Feature is usable but there are warnings: %s', 'elasticpress' ), $status->message ) ); } - $feature_settings = ( ! empty( $active_features[ $feature->slug ] ) ) ? $active_features[ $feature->slug ] : array(); - - $active_features[ $feature->slug ] = wp_parse_args( $feature_settings, $feature->default_settings ); - $active_features[ $feature->slug ]['active'] = true; - - $feature->post_activation(); + ep_activate_feature( $feature->slug ); if ( $feature->requires_install_reindex ) { WP_CLI::warning( __( 'This feature requires a re-index. You may want to run the index command next.', 'elasticpress' ) ); } - if ( ! empty( $assoc_args['network-wide'] ) ) { - update_site_option( 'ep_feature_settings', $active_features ); - } else { - update_option( 'ep_feature_settings', $active_features ); - } - WP_CLI::success( __( 'Feature activated', 'elasticpress' ) ); } @@ -122,17 +105,11 @@ public function deactivate_feature( $args, $assoc_args ) { $key = array_search( $feature->slug, array_keys( $active_features ) ); - if ( false !== $key && $active_features[ $feature->slug ]['active'] ) { - $active_features[ $feature->slug ]['active'] = false; - } else { + if ( false === $key || empty( $active_features[ $feature->slug ]['active'] ) ) { WP_CLI::error( __( 'Feature is not active', 'elasticpress' ) ); } - if ( ! empty( $assoc_args['network-wide'] ) ) { - update_site_option( 'ep_feature_settings', $active_features ); - } else { - update_option( 'ep_feature_settings', $active_features ); - } + ep_deactivate_feature( $feature->slug ); WP_CLI::success( __( 'Feature deactivated', 'elasticpress' ) ); } diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index 461bec5e17..caf4ccc9b5 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -607,36 +607,15 @@ public function action_wp_ajax_ep_save_feature() { exit; } - $feature = ep_get_registered_feature( $_POST['feature'] ); - $original_state = $feature->is_active(); + $data = ep_update_feature( $_POST['feature'], $_POST['settings'] ); - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - $feature_settings = get_site_option( 'ep_feature_settings', array() ); - } else { - $feature_settings = get_option( 'ep_feature_settings', array() ); - } - - $feature_settings[ $_POST['feature'] ] = wp_parse_args( $_POST['settings'], $feature->default_settings ); - $feature_settings[ $_POST['feature'] ]['active'] = (bool) $feature_settings[ $_POST['feature'] ]['active']; - - $sanitize_feature_settings = apply_filters( 'ep_sanitize_feature_settings', $feature_settings, $feature ); - - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_feature_settings', $sanitize_feature_settings ); - } else { - update_option( 'ep_feature_settings', $sanitize_feature_settings ); - } - - $data = array( - 'reindex' => false, - ); - - if ( $feature_settings[ $_POST['feature'] ]['active'] && ! $original_state ) { - if ( ! empty( $feature->requires_install_reindex ) ) { - $data['reindex'] = true; + // Since we deactivated, delete auto activate notice + if ( empty( $_POST['settings']['active'] ) ) { + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { + delete_site_option( 'ep_feature_auto_activated_sync' ); + } else { + delete_option( 'ep_feature_auto_activated_sync' ); } - - $feature->post_activation(); } wp_send_json_success( $data ); diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index 40055082ba..1eb0658edf 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -69,32 +69,52 @@ public function register_feature( $slug, $feature_args ) { * Activate or deactivate a feature * * @param string $slug - * @param boolean $active + * @param array $settings * @since 2.2 + * @return array */ - public function activate_feature( $slug, $active = true ) { + public function update_feature( $slug, $settings ) { + $feature = ep_get_registered_feature( $slug ); + $original_state = $feature->is_active(); + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { $feature_settings = get_site_option( 'ep_feature_settings', array() ); } else { $feature_settings = get_option( 'ep_feature_settings', array() ); } - $feature = $this->registered_features[ $slug ]; + if ( empty( $feature_settings[ $slug ] ) ) { + // If doesn't exist, merge with feature defaults + $feature_settings[ $slug ] = wp_parse_args( $settings, $feature->default_settings ); + } else { + // If exist just merge changed values into current + $feature_settings[ $slug ] = wp_parse_args( $settings, $feature_settings[ $slug ] ); + } - $current_feature_settings = ( ! empty( $feature_settings[ $slug ] ) ) ? $feature_settings[ $slug ] : array(); + // Make sure active is a proper bool + $feature_settings[ $slug ]['active'] = (bool) $feature_settings[ $slug ]['active']; - $feature_settings[ $slug ] = wp_parse_args( $current_feature_settings, $feature->default_settings ); - $feature_settings[ $slug ]['active'] = (bool) $active; + $sanitize_feature_settings = apply_filters( 'ep_sanitize_feature_settings', $feature_settings, $feature ); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { - update_site_option( 'ep_feature_settings', $feature_settings ); + update_site_option( 'ep_feature_settings', $sanitize_feature_settings ); } else { - update_option( 'ep_feature_settings', $feature_settings ); + update_option( 'ep_feature_settings', $sanitize_feature_settings ); } - if ( $active ) { + $data = array( + 'reindex' => false, + ); + + if ( $feature_settings[ $slug ]['active'] && ! $original_state ) { + if ( ! empty( $feature->requires_install_reindex ) ) { + $data['reindex'] = true; + } + $feature->post_activation(); } + + return $data; } /** @@ -178,6 +198,8 @@ public function handle_feature_activation() { $active = ( 0 === $code ); if ( ! $feature->is_active() && $active ) { + ep_activate_feature( $slug ); + // Need to activate and maybe set a sync notice if ( $feature->requires_install_reindex ) { if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { @@ -186,11 +208,9 @@ public function handle_feature_activation() { update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) ); } } - - ep_activate_feature( $slug, $active ); } elseif ( $feature->is_active() && ! $active ) { // Just deactivate - ep_activate_feature( $slug, $active ); + ep_deactivate_feature( $slug ); } } } @@ -245,6 +265,18 @@ function ep_register_feature( $slug, $feature_args ) { return EP_Features::factory()->register_feature( $slug, $feature_args ); } +/** + * Update a feature + * + * @param string $slug + * @param array $settings + * @since 2.2 + * @return array + */ +function ep_update_feature( $slug, $settings ) { + return EP_Features::factory()->update_feature( $slug, $settings ); +} + /** * Activate a feature * @@ -252,8 +284,8 @@ function ep_register_feature( $slug, $feature_args ) { * @param bool $active * @since 2.2 */ -function ep_activate_feature( $slug, $active = true ) { - EP_Features::factory()->activate_feature( $slug, $active ); +function ep_activate_feature( $slug ) { + EP_Features::factory()->update_feature( $slug, array( 'active' => true ) ); } /** @@ -263,7 +295,7 @@ function ep_activate_feature( $slug, $active = true ) { * @since 2.2 */ function ep_deactivate_feature( $slug ) { - EP_Features::factory()->activate_feature( $slug, false ); + EP_Features::factory()->update_feature( $slug, array( 'active' => false ) ); } /** diff --git a/features/protected-content/protected-content.php b/features/protected-content/protected-content.php index 79ce1e5d62..608bbca44e 100644 --- a/features/protected-content/protected-content.php +++ b/features/protected-content/protected-content.php @@ -152,7 +152,7 @@ function ep_pc_requirements_status( $status ) { /** * Register the feature */ -ep_register_feature( 'protected-content', array( +ep_register_feature( 'protected_content', array( 'title' => esc_html__( 'Protected Content', 'elasticpress' ), 'setup_cb' => 'ep_pc_setup', 'requirements_status_cb' => 'ep_pc_requirements_status', From 29089eb84be3323c15bc8491af37f9b44d7a601d Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 20 Feb 2017 23:45:20 -0500 Subject: [PATCH 147/159] Dont update if feature doesn't exist --- classes/class-ep-features.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/classes/class-ep-features.php b/classes/class-ep-features.php index 1eb0658edf..9d786f5b7d 100644 --- a/classes/class-ep-features.php +++ b/classes/class-ep-features.php @@ -71,10 +71,15 @@ public function register_feature( $slug, $feature_args ) { * @param string $slug * @param array $settings * @since 2.2 - * @return array + * @return array|bool */ public function update_feature( $slug, $settings ) { $feature = ep_get_registered_feature( $slug ); + + if ( empty( $feature ) ) { + return false; + } + $original_state = $feature->is_active(); if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { From 4a2c47ca32fecec11b0b81593d266a782624599e Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 20 Feb 2017 23:45:30 -0500 Subject: [PATCH 148/159] Rename protected content test slug --- tests/features/test-protected-content.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/features/test-protected-content.php b/tests/features/test-protected-content.php index 9c9f1f82f6..eb9ecbe5b6 100644 --- a/tests/features/test-protected-content.php +++ b/tests/features/test-protected-content.php @@ -82,7 +82,7 @@ public function testAdminNotOn() { public function testAdminOn() { set_current_screen( 'edit.php' ); - ep_activate_feature( 'protected-content' ); + ep_activate_feature( 'protected_content' ); EP_Features::factory()->setup_features(); ep_create_and_sync_post(); @@ -111,7 +111,7 @@ public function testAdminOn() { public function testAdminOnDraft() { set_current_screen( 'edit.php' ); - ep_activate_feature( 'protected-content' ); + ep_activate_feature( 'protected_content' ); EP_Features::factory()->setup_features(); ep_create_and_sync_post(); @@ -147,7 +147,7 @@ public function testAdminOnDraft() { public function testAdminOnDraftUpdated() { set_current_screen( 'edit.php' ); - ep_activate_feature( 'protected-content' ); + ep_activate_feature( 'protected_content' ); EP_Features::factory()->setup_features(); ep_create_and_sync_post(); @@ -185,7 +185,7 @@ public function testAdminOnDraftUpdated() { public function testAdminCategories() { set_current_screen( 'edit.php' ); - ep_activate_feature( 'protected-content' ); + ep_activate_feature( 'protected_content' ); EP_Features::factory()->setup_features(); $cat1 = wp_create_category( 'category one' ); From 174935ec57d73afd8027cfe2ba1c485610298d91 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 20 Feb 2017 23:46:42 -0500 Subject: [PATCH 149/159] Fix protected content slug in WC test --- tests/features/test-woocommerce.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/features/test-woocommerce.php b/tests/features/test-woocommerce.php index 9cd57807f7..3df97fe201 100644 --- a/tests/features/test-woocommerce.php +++ b/tests/features/test-woocommerce.php @@ -138,7 +138,7 @@ public function testProductsPostTypeQueryProductCatTax() { * @group woocommerce */ public function testSearchOnShopOrderAdmin() { - ep_activate_feature( 'admin' ); + ep_activate_feature( 'protected_content' ); ep_activate_feature( 'woocommerce' ); EP_Features::factory()->setup_features(); From 1268d361feaec782b59fcf4362d790ba1687e3bf Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Tue, 21 Feb 2017 11:55:11 -0500 Subject: [PATCH 150/159] Fix annoying dashboard sync delay --- assets/js/dashboard.min.js | 2 +- assets/js/src/dashboard.js | 26 +++++++++++++++++++++++--- classes/class-ep-dashboard.php | 1 + 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/assets/js/dashboard.min.js b/assets/js/dashboard.min.js index 2172bd2b69..7cbfba1d8c 100644 --- a/assets/js/dashboard.min.js +++ b/assets/js/dashboard.min.js @@ -1 +1 @@ -!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("sync"===o){var b=epDash.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=epDash.sync_paused+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=epDash.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(epDash.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null}else if("finished"===o){if(j.text(epDash.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:epDash.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",feature_sync:p,nonce:epDash.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-features")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-feature"),e=g.find(".ep-feature-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_feature",feature:c,nonce:epDash.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("feature-active"):e.removeClass("feature-active"),a.data.reindex&&(o="sync",e.addClass("feature-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("feature-active"),e.removeClass("feature-syncing")},700)})}),epDash.index_meta?epDash.index_meta.wpcli_sync?(o="wpcli",b()):(q=epDash.index_meta.offset,r=epDash.index_meta.found_posts,epDash.index_meta.feature_sync&&(p=epDash.index_meta.feature_sync),epDash.index_meta.current_site&&(e=epDash.index_meta.current_site),epDash.index_meta.site_stack&&(f=epDash.index_meta.site_stack),f&&f.length?epDash.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):0!==r||epDash.index_meta.start?epDash.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):(o="finished",b())):epDash.auto_start_index&&(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()),k.on("click",function(){o="sync",a('[data-ep-notice="no-sync"], [data-ep-notice="auto-activate-sync"], [data-ep-notice="upgrade-sync"]').remove(),d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file +!function(a){function b(){if(0===q)i.css({width:"1%"});else{var a=parseInt(q)/parseInt(r)*100;i.css({width:a+"%"})}if("initialsync"===o){var b=epDash.sync_initial;j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("sync"===o){var b=epDash.sync_syncing+" "+parseInt(q)+"/"+parseInt(r);e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.show(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("pause"===o){var b=epDash.sync_paused;r&&0!==r&&(b+=" "+parseInt(q)+"/"+parseInt(r)),e&&(b+=" ("+e.url+")"),j.text(b),j.show(),i.show(),m.hide(),h.addClass("syncing"),n.show(),l.show(),k.hide()}else if("wpcli"===o){var b=epDash.sync_wpcli;j.text(b),j.show(),i.hide(),m.hide(),h.addClass("syncing"),n.hide(),l.hide(),k.hide()}else if("error"===o){if(j.text(epDash.sync_error),j.show(),k.show(),n.hide(),l.hide(),m.hide(),h.removeClass("syncing"),i.hide(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}else if("cancel"===o){if(j.hide(),i.hide(),m.hide(),h.removeClass("syncing"),n.hide(),l.hide(),k.show(),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null}else if("finished"===o){if(j.text(epDash.sync_complete),j.show(),i.hide(),m.hide(),n.hide(),l.hide(),k.show(),h.removeClass("syncing"),p){var c=g.find(".ep-feature-"+p);c.removeClass("feature-syncing")}p=null,setTimeout(function(){j.hide()},7e3)}}function c(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_cancel_index",nonce:epDash.nonce}})}function d(){a.ajax({method:"post",url:ajaxurl,data:{action:"ep_index",feature_sync:p,nonce:epDash.nonce}}).done(function(a){return"sync"===o?(r=a.data.found_posts,q=a.data.offset,a.data.site_stack&&(f=a.data.site_stack),a.data.current_site&&(e=a.data.current_site),f&&f.length?(o="sync",b(),void d()):void(0!==a.data.found_posts||a.data.start?(o="sync",b(),d()):(o="finished",b()))):void 0}).error(function(a){a&&a.status&&parseInt(a.status)>=400&&parseInt(a.status)<600&&(o="error",b(),c())})}var e,f,g=a(document.getElementsByClassName("ep-features")),h=a(document.getElementsByClassName("error-overlay")),i=a(document.getElementsByClassName("progress-bar")),j=a(document.getElementsByClassName("sync-status")),k=a(document.getElementsByClassName("start-sync")),l=a(document.getElementsByClassName("resume-sync")),m=a(document.getElementsByClassName("pause-sync")),n=a(document.getElementsByClassName("cancel-sync")),o="sync",p=!1,q=0,r=0;g.on("click",".learn-more, .collapse",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-full")}),g.on("click",".settings-button",function(b){$feature=a(this).parents(".ep-feature"),$feature.toggleClass("show-settings")}),g.on("click",".save-settings",function(b){b.preventDefault();var c=b.target.getAttribute("data-feature"),e=g.find(".ep-feature-"+c),f={},h=e.find(".setting-field");h.each(function(){var b=a(this).attr("type"),c=a(this).attr("data-field-name"),d=a(this).attr("value");"radio"===b&&a(this).attr("checked")&&(f[c]=d)}),e.addClass("saving"),a.ajax({method:"post",url:ajaxurl,data:{action:"ep_save_feature",feature:c,nonce:epDash.nonce,settings:f}}).done(function(a){setTimeout(function(){e.removeClass("saving"),"1"===f.active?e.addClass("feature-active"):e.removeClass("feature-active"),a.data.reindex&&(o="sync",e.addClass("feature-syncing"),p=c,d())},700)}).error(function(){setTimeout(function(){e.removeClass("saving"),e.removeClass("feature-active"),e.removeClass("feature-syncing")},700)})}),epDash.index_meta?epDash.index_meta.wpcli_sync?(o="wpcli",b()):(q=epDash.index_meta.offset,r=epDash.index_meta.found_posts,epDash.index_meta.feature_sync&&(p=epDash.index_meta.feature_sync),epDash.index_meta.current_site&&(e=epDash.index_meta.current_site),epDash.index_meta.site_stack&&(f=epDash.index_meta.site_stack),f&&f.length?epDash.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):0!==r||epDash.index_meta.start?epDash.auto_start_index?(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()):(o="pause",b()):(o="finished",b())):epDash.auto_start_index&&(o="sync",history.pushState({},document.title,document.location.pathname+document.location.search.replace(/&do_sync/,"")),b(),d()),k.on("click",function(){o="initialsync",b(),a('[data-ep-notice="no-sync"], [data-ep-notice="auto-activate-sync"], [data-ep-notice="upgrade-sync"]').remove(),o="sync",d()}),m.on("click",function(){o="pause",b()}),l.on("click",function(){o="sync",b(),d()}),n.on("click",function(){o="cancel",b(),c()})}(jQuery); \ No newline at end of file diff --git a/assets/js/src/dashboard.js b/assets/js/src/dashboard.js index cae816294a..fdea636fc7 100644 --- a/assets/js/src/dashboard.js +++ b/assets/js/src/dashboard.js @@ -162,7 +162,20 @@ $progressBar.css( { width: width + '%' } ); } - if ( 'sync' === syncStatus ) { + if ( 'initialsync' === syncStatus ) { + var text = epDash.sync_initial; + + $syncStatusText.text( text ); + + $syncStatusText.show(); + $progressBar.show(); + $pauseSyncButton.show(); + $errorOverlay.addClass( 'syncing' ); + + $cancelSyncButton.hide(); + $resumeSyncButton.hide(); + $startSyncButton.hide(); + } else if ( 'sync' === syncStatus ) { var text = epDash.sync_syncing + ' ' + parseInt( processed ) + '/' + parseInt( toProcess ); if ( currentSite ) { @@ -180,7 +193,11 @@ $resumeSyncButton.hide(); $startSyncButton.hide(); } else if ( 'pause' === syncStatus ) { - var text = epDash.sync_paused + ' ' + parseInt( processed ) + '/' + parseInt( toProcess ); + var text = epDash.sync_paused; + + if ( toProcess && 0 !== toProcess ) { + text += ' ' + parseInt( processed ) + '/' + parseInt( toProcess ); + } if ( currentSite ) { text += ' (' + currentSite.url + ')' @@ -336,11 +353,14 @@ } $startSyncButton.on( 'click', function() { - syncStatus = 'sync'; + syncStatus = 'initialsync'; + + updateSyncDash(); // On initial sync, remove dashboard warnings that dont make sense $( '[data-ep-notice="no-sync"], [data-ep-notice="auto-activate-sync"], [data-ep-notice="upgrade-sync"]').remove(); + syncStatus = 'sync'; sync(); } ); diff --git a/classes/class-ep-dashboard.php b/classes/class-ep-dashboard.php index caf4ccc9b5..8b38a7dc55 100644 --- a/classes/class-ep-dashboard.php +++ b/classes/class-ep-dashboard.php @@ -666,6 +666,7 @@ public function action_admin_enqueue_dashboard_scripts() { $data['sync_complete'] = esc_html__( 'Sync complete', 'elasticpress' ); $data['sync_paused'] = esc_html__( 'Sync paused', 'elasticpress' ); $data['sync_syncing'] = esc_html__( 'Syncing', 'elasticpress' ); + $data['sync_initial'] = esc_html__( 'Starting sync', 'elasticpress' ); $data['sync_wpcli'] = esc_html__( "WP CLI sync is occuring. Refresh the page to see if it's finished", 'elasticpress' ); $data['sync_error'] = esc_html__( 'An error occured while syncing', 'elasticpress' ); From 967b05f66fac63c5461996ed12643fe8f85bc74f Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 24 Feb 2017 13:49:52 -0800 Subject: [PATCH 151/159] Delay execution of search setup to later since there is a dependent filter in protected-content --- features/search/search.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/features/search/search.php b/features/search/search.php index da5799a2fa..cf65df16a0 100644 --- a/features/search/search.php +++ b/features/search/search.php @@ -33,6 +33,16 @@ function ep_search_feature_box_long() { 'Search', - 'setup_cb' => 'ep_search_setup', + 'setup_cb' => 'ep_delay_search_setup', 'feature_box_summary_cb' => 'ep_search_feature_box_summary', 'feature_box_long_cb' => 'ep_search_feature_box_long', 'requires_install_reindex' => false, From c8b9e2d5e35ac0bcedcea29b1e8fe934925e6ad3 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Fri, 24 Feb 2017 13:52:47 -0800 Subject: [PATCH 152/159] Disable search integration when a fields WP_Query argument is used --- features/search/search.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/features/search/search.php b/features/search/search.php index cf65df16a0..188d04bac2 100644 --- a/features/search/search.php +++ b/features/search/search.php @@ -237,6 +237,12 @@ function ep_integrate_search_queries( $enabled, $query ) { $enabled = false; } else if ( method_exists( $query, 'is_search' ) && $query->is_search() && ! empty( $query->query_vars['s'] ) ) { $enabled = true; + + $fields = $query->get( 'fields' ); + + if ( ! empty( $fields ) ) { + $enabled = false; + } } return $enabled; From ed427e09e8c84dcd16c4fc4b08508d33007ee7b0 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Sun, 26 Feb 2017 14:22:14 -0800 Subject: [PATCH 153/159] Add related posts api documentation --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index a53ad43c45..ae198f25d5 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,12 @@ Allow customers to filter through products faster and improve product search rel Help users easily find related content by adding related posts to the end of each post. +Available API functions: + +* `ep_find_related( $post_id, $return = 5 )` + + Get related posts for a given `$post_id`. Use this in a theme or plugin to get related content. + ### Protected Content Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. Enabling this feature will allow other ElasticPress features to work within the admin (i.e. WooCommerce and Search). You'll need to make sure your Elasticsearch instance is properly secured. From d04bc2f9356fa001ec1b01406d80b2b09b34ae23 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Mon, 27 Feb 2017 11:02:52 -0800 Subject: [PATCH 154/159] Clean up feature language; rebuild pot --- README.md | 8 +- .../protected-content/protected-content.php | 7 +- features/related-posts/related-posts.php | 6 +- features/search/search.php | 8 +- features/woocommerce/woocommerce.php | 6 +- lang/elasticpress.pot | 296 +++++++++--------- readme.txt | 8 +- 7 files changed, 163 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index ae198f25d5..475bf49ad9 100644 --- a/README.md +++ b/README.md @@ -36,15 +36,15 @@ Once syncing finishes, your site is officially supercharged. You also have acces ### Search -Beef up your search to be more accurate, search tags, categories, and other taxonomies, catch misspellings, weight content by recency and more. +Instantly find the content you’re looking for. The first time. ### WooCommerce -Allow customers to filter through products faster and improve product search relevancy. This feature will increase your sales bottom line and reduce administrative costs. +“I want a cotton, woman’s t-shirt, for under $15 that’s in stock.” Faceted product browsing strains servers and increases load times. Your buyers can find the perfect product quickly, and buy it quickly. ### Related Posts -Help users easily find related content by adding related posts to the end of each post. +ElasticPress understands data in real time, so it can instantly deliver engaging and precise related content with no impact on site performance. Available API functions: @@ -54,7 +54,7 @@ Available API functions: ### Protected Content -Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. Enabling this feature will allow other ElasticPress features to work within the admin (i.e. WooCommerce and Search). You'll need to make sure your Elasticsearch instance is properly secured. +Optionally index all of your content, including private and unpublished content, to speed up searches and queries in places like the administrative dashboard. ## `WP_Query` and the ElasticPress Query Integration diff --git a/features/protected-content/protected-content.php b/features/protected-content/protected-content.php index 608bbca44e..78ddf6aefe 100644 --- a/features/protected-content/protected-content.php +++ b/features/protected-content/protected-content.php @@ -98,7 +98,7 @@ function ep_pc_integrate( $query ) { */ function ep_pc_feature_box_summary() { ?> -

+

-

- -

- +

We recommend using a secured Elasticsearch setup, such as ElasticPress.io, to prevent potential exposure of content not intended for the public.', 'elasticpress' ); ?>

-

+

-

- -

+

API functions.', 'elasticpress' ); ?>

-

+

-

- -

- -

+

-

+

-

- -

+

\n" "Language-Team: LANGUAGE \n" "X-Generator: grunt-wp-i18n 0.5.4\n" -#: bin/wp-cli.php:46 bin/wp-cli.php:93 +#: bin/wp-cli.php:67 bin/wp-cli.php:104 msgid "No feature with that slug is registered" msgstr "" -#: bin/wp-cli.php:56 +#: bin/wp-cli.php:71 msgid "This feature is already active" msgstr "" -#: bin/wp-cli.php:60 -msgid "Feature depedencies are not met" +#: bin/wp-cli.php:77 +msgid "Feature requirements are not met" +msgstr "" + +#: bin/wp-cli.php:79 +msgid "Feature is usable but there are warnings: %s" msgstr "" -#: bin/wp-cli.php:68 +#: bin/wp-cli.php:85 msgid "" "This feature requires a re-index. You may want to run the index command " "next." msgstr "" -#: bin/wp-cli.php:77 +#: bin/wp-cli.php:88 msgid "Feature activated" msgstr "" -#: bin/wp-cli.php:107 +#: bin/wp-cli.php:116 msgid "Feature is not active" msgstr "" -#: bin/wp-cli.php:116 +#: bin/wp-cli.php:121 msgid "Feature deactivated" msgstr "" -#: bin/wp-cli.php:137 +#: bin/wp-cli.php:141 msgid "Active features:" msgstr "" -#: bin/wp-cli.php:139 +#: bin/wp-cli.php:149 msgid "Registered features:" msgstr "" -#: bin/wp-cli.php:170 +#: bin/wp-cli.php:180 msgid "Adding mapping for site %d..." msgstr "" -#: bin/wp-cli.php:180 bin/wp-cli.php:198 +#: bin/wp-cli.php:190 bin/wp-cli.php:208 msgid "Mapping sent" msgstr "" -#: bin/wp-cli.php:182 bin/wp-cli.php:200 +#: bin/wp-cli.php:192 bin/wp-cli.php:210 msgid "Mapping failed" msgstr "" -#: bin/wp-cli.php:188 +#: bin/wp-cli.php:198 msgid "Adding mapping..." msgstr "" -#: bin/wp-cli.php:228 +#: bin/wp-cli.php:238 msgid "Deleting index for site %d..." msgstr "" -#: bin/wp-cli.php:233 bin/wp-cli.php:246 +#: bin/wp-cli.php:243 bin/wp-cli.php:256 msgid "Index deleted" msgstr "" -#: bin/wp-cli.php:235 +#: bin/wp-cli.php:245 msgid "Delete index failed" msgstr "" -#: bin/wp-cli.php:241 +#: bin/wp-cli.php:251 msgid "Deleting index..." msgstr "" -#: bin/wp-cli.php:248 +#: bin/wp-cli.php:258 msgid "Index delete failed" msgstr "" -#: bin/wp-cli.php:266 bin/wp-cli.php:380 +#: bin/wp-cli.php:276 bin/wp-cli.php:407 msgid "Recreating network alias..." msgstr "" -#: bin/wp-cli.php:273 bin/wp-cli.php:407 +#: bin/wp-cli.php:283 bin/wp-cli.php:434 msgid "Done!" msgstr "" -#: bin/wp-cli.php:275 +#: bin/wp-cli.php:285 msgid "An error occurred" msgstr "" -#: bin/wp-cli.php:360 +#: bin/wp-cli.php:387 msgid "Indexing posts network-wide..." msgstr "" -#: bin/wp-cli.php:371 bin/wp-cli.php:392 +#: bin/wp-cli.php:398 bin/wp-cli.php:419 msgid "Number of posts indexed on site %d: %d" msgstr "" -#: bin/wp-cli.php:374 bin/wp-cli.php:395 +#: bin/wp-cli.php:401 bin/wp-cli.php:422 msgid "Number of post index errors on site %d: %d" msgstr "" -#: bin/wp-cli.php:384 +#: bin/wp-cli.php:411 msgid "Total number of posts indexed: %d" msgstr "" -#: bin/wp-cli.php:388 +#: bin/wp-cli.php:415 msgid "Indexing posts..." msgstr "" -#: bin/wp-cli.php:399 +#: bin/wp-cli.php:426 msgid "Total time elapsed: " msgstr "" -#: bin/wp-cli.php:665 +#: bin/wp-cli.php:717 msgid "" "The following posts failed to index:\r\n" "\r\n" msgstr "" -#: bin/wp-cli.php:802 +#: bin/wp-cli.php:867 msgid "" "There is no Elasticsearch host set up. Either add one through the dashboard " "or define one in wp-config.php" msgstr "" -#: bin/wp-cli.php:804 +#: bin/wp-cli.php:869 msgid "Unable to reach Elasticsearch Server! Check that service is running." msgstr "" -#: classes/class-ep-api.php:2027 +#: classes/class-ep-api.php:2119 msgid "" "Invalid response from ElasticPress server. Please contact your " "administrator." msgstr "" -#: classes/class-ep-api.php:2040 +#: classes/class-ep-api.php:2132 msgid "" "Site not indexed.

Please run: wp elasticpress index --setup " "--network-wide using WP-CLI. Or use the index button on the left of " "this screen.

" msgstr "" -#: classes/class-ep-api.php:2044 +#: classes/class-ep-api.php:2136 msgid "" "Site not indexed.

Please run: wp elasticpress index --setup " "using WP-CLI. Or use the index button on the left of this screen.

" msgstr "" -#: classes/class-ep-api.php:2082 classes/class-ep-api.php:2141 -#: classes/class-ep-api.php:2181 classes/class-ep-api.php:2232 +#: classes/class-ep-api.php:2215 classes/class-ep-api.php:2255 +#: classes/class-ep-api.php:2306 msgid "Elasticsearch Host is not available." msgstr "" -#: classes/class-ep-dashboard.php:80 +#: classes/class-ep-dashboard.php:96 msgid "Dashboard" msgstr "" -#: classes/class-ep-dashboard.php:118 +#: classes/class-ep-dashboard.php:251 msgid "" -"ElasticPress is in the middle of a sync. The plugin wont work until it " -"finishes. Want to go back and finish it?" +"There is a problem with connecting to your Elasticsearch host. You will " +"need to fix it for ElasticPress to work." msgstr "" -#: classes/class-ep-dashboard.php:167 +#: classes/class-ep-dashboard.php:258 msgid "" -"There is a problem with connecting to your Elasticsearch host. You will " -"need to fix it for ElasticPress to work." +"Your Elasticsearch version %s is above the maximum required Elasticsearch " +"version %s. ElasticPress may or may not work properly." +msgstr "" + +#: classes/class-ep-dashboard.php:265 +msgid "" +"Your Elasticsearch version %s is below the minimum required Elasticsearch " +"version %s. ElasticPress may or may not work properly." msgstr "" -#: classes/class-ep-dashboard.php:457 +#: classes/class-ep-dashboard.php:278 +msgid "" +"Thanks for installing ElasticPress! You will need to run through a quick set up process to get the plugin working." +msgstr "" + +#: classes/class-ep-dashboard.php:291 +msgid "" +"ElasticPress is almost ready. You will need to complete a sync to get the plugin working." +msgstr "" + +#: classes/class-ep-dashboard.php:304 +msgid "" +"The new version of ElasticPress requires that you run a " +"sync." +msgstr "" + +#: classes/class-ep-dashboard.php:319 +msgid "" +"The ElasticPress %s feature has been auto-activated! You will need to run a sync for it to work." +msgstr "" + +#: classes/class-ep-dashboard.php:666 msgid "Sync complete" msgstr "" -#: classes/class-ep-dashboard.php:458 +#: classes/class-ep-dashboard.php:667 msgid "Sync paused" msgstr "" -#: classes/class-ep-dashboard.php:459 +#: classes/class-ep-dashboard.php:668 msgid "Syncing" msgstr "" -#: classes/class-ep-dashboard.php:460 +#: classes/class-ep-dashboard.php:669 +msgid "Starting sync" +msgstr "" + +#: classes/class-ep-dashboard.php:670 msgid "WP CLI sync is occuring. Refresh the page to see if it's finished" msgstr "" -#: classes/class-ep-dashboard.php:461 +#: classes/class-ep-dashboard.php:671 msgid "An error occured while syncing" msgstr "" -#: classes/class-ep-dashboard.php:481 +#: classes/class-ep-dashboard.php:708 msgid "Security error!" msgstr "" -#: classes/class-ep-dashboard.php:590 classes/class-ep-dashboard.php:591 +#: classes/class-ep-dashboard.php:822 classes/class-ep-dashboard.php:823 #: classes/class-ep-feature.php:254 includes/settings-page.php:27 #: vendor/woocommerce/includes/admin/class-wc-admin-menus.php:80 #: vendor/woocommerce/includes/admin/settings/class-wc-settings-api.php:45 @@ -213,7 +251,7 @@ msgstr "" msgid "Settings" msgstr "" -#: classes/class-ep-dashboard.php:599 classes/class-ep-dashboard.php:600 +#: classes/class-ep-dashboard.php:831 classes/class-ep-dashboard.php:832 msgid "Welcome" msgstr "" @@ -279,50 +317,44 @@ msgstr "" msgid "The following values do not describe a valid date: month %1$s, day %2$s." msgstr "" -#: features/admin/admin.php:81 -msgid "" -"Help editors more effectively browse through content. Load long lists of " -"posts faster. Filter posts faster. Please note this syncs draft content to " -"Elasticsearch. You want to make sure your Elasticsearch instance is " -"properly secured." -msgstr "" - -#: features/admin/admin.php:92 +#: features/protected-content/protected-content.php:101 msgid "" -"Within the admin panel, posts and pages are shown in a standarized easy to " -"use table format. After activating an SEO plugin, increasing post per " -"pages, and making other modifications, that table view loads very slowly." +"Optionally index all of your content, including private and unpublished " +"content, to speed up searches and queries in places like the administrative " +"dashboard." msgstr "" -#: features/admin/admin.php:94 +#: features/protected-content/protected-content.php:112 msgid "" -"ElasticPress admin will make your admin curation experience much faster and " -"easier. No longer will you have to wait 60 seconds to do things that should " -"be easy such as viewing 200 posts at once." +"Securely indexes unpublished content—including private, draft, and " +"scheduled posts —improving load times in places like the administrative " +"dashboard where WordPress needs to include protected content in a query. " +"We recommend using a secured Elasticsearch setup, such as " +"ElasticPress.io, to prevent potential exposure of content not intended for " +"the public." msgstr "" -#: features/admin/admin.php:126 +#: features/protected-content/protected-content.php:143 msgid "" "You aren't using ElasticPress.io so " "we can't be sure your Elasticsearch instance is secure." msgstr "" -#: features/related-posts/related-posts.php:76 -msgid "Help users easily find related content with a widget that just works." +#: features/protected-content/protected-content.php:153 +msgid "Protected Content" msgstr "" -#: features/related-posts/related-posts.php:87 +#: features/related-posts/related-posts.php:76 msgid "" -"Showing users related content is a quick way to improve readership and " -"loyalty. There are a number of plugins that show related content, most of " -"which are ineffective and slow." +"ElasticPress understands data in real time, so it can instantly deliver " +"engaging and precise related content with no impact on site performance." msgstr "" -#: features/related-posts/related-posts.php:89 +#: features/related-posts/related-posts.php:87 msgid "" -"ElasticPress has a powerful content matching algorithm that lets it find " -"related content very effectively. This feature will create a widget for you " -"to place into any sidebar or widgetized area." +"Output related content using our Widget or directly in your theme using our " +"API " +"functions." msgstr "" #: features/related-posts/widget.php:17 @@ -344,54 +376,36 @@ msgid "Number of Posts to Show:" msgstr "" #: features/search/search.php:16 -msgid "" -"Beef up your search to be more accurate, search tags, categories, and other " -"taxonomies, catch misspellings, weight content by recency and more." +msgid "Instantly find the content you’re looking for. The first time." msgstr "" #: features/search/search.php:27 msgid "" -"Search is a long neglected piece of WordPress. Result relevancy is poor; " -"performance is poor; there is no handling of misspellings; there is no way " -"to search categories, tags, or custom taxonomies as WordPress by default " -"only searches post content, excerpt, and title." -msgstr "" - -#: features/search/search.php:30 -msgid "" -"The search feature allows you to do all these things and more. Just " -"activating the feature will make your search experience much better. Your " -"users will be able to more effectively browse your website and find the " -"content they desire. Misspellings will be accounted for, categories " -"searched, and results weighted by recency. If activated in conjunction with " -"the admin feature, admin search will be improved as well." +"Overcome higher-end performance and functional limits posed by the " +"traditional WordPress structured (SQL) database to deliver superior keyword " +"search, instantly. ElasticPress indexes custom fields, tags, and other " +"metadata to improve search results. Fuzzy matching accounts for " +"misspellings and verb tenses." msgstr "" -#: features/woocommerce/woocommerce.php:533 +#: features/woocommerce/woocommerce.php:528 msgid "" -"Allow customers to filter through products faster and improve product " -"search relevancy. Enable editors to find orders and products more " -"effectively in the admin. This feature will increase your sales bottom line " -"and reduce administrative costs." +"“I want a cotton, woman’s t-shirt, for under $15 that’s in stock.” Faceted " +"product browsing strains servers and increases load times. Your buyers can " +"find the perfect product quickly, and buy it quickly." msgstr "" -#: features/woocommerce/woocommerce.php:544 +#: features/woocommerce/woocommerce.php:539 msgid "" -"Running eCommerce stores is hard enough already. You should not have to " -"worry about slow load times. ElasticPress WooCommerce supercharges all " -"product queries, product sorts, and filters both on the front end and the " -"admin. No matter how many products or filters you have, your site will load " -"fast." +"Most caching and performance tools can’t keep up with the nearly infinite " +"ways your visitors might filter or navigate your products. No matter how " +"many products, filters, or customers you have, ElasticPress will keep your " +"online store performing quickly. If used in combination with the Protected " +"Content feature, ElasticPress will also accelerate order searches and back " +"end product management." msgstr "" -#: features/woocommerce/woocommerce.php:546 -msgid "" -"In the admin, order management and fulfillment is supercharged. Finding " -"orders is much easier with more relevant searches. View order lists is " -"easier since they load faster." -msgstr "" - -#: features/woocommerce/woocommerce.php:560 +#: features/woocommerce/woocommerce.php:553 msgid "WooCommerce not installed." msgstr "" @@ -20847,86 +20861,70 @@ msgstr "" msgid "Only logged in customers who have purchased this product may leave a review." msgstr "" -#: vendor/wp-cli/wp-cli/php/WP_CLI/CommandWithTerms.php:211 -msgid "Invalid Taxonomy" -msgstr "" - -#: vendor/wp-cli/wp-cli/php/WP_CLI/CommandWithUpgrade.php:446 +#: vendor/wp-cli/wp-cli/php/WP_CLI/CommandWithUpgrade.php:473 msgid " Try again" msgstr "" -#: vendor/wp-cli/wp-cli/php/WP_CLI/CommandWithUpgrade.php:451 +#: vendor/wp-cli/wp-cli/php/WP_CLI/CommandWithUpgrade.php:478 msgid "API error. Try Again." msgstr "" -#: vendor/wp-cli/wp-cli/php/commands/core.php:818 +#: vendor/wp-cli/wp-cli/php/commands/core.php:752 msgid "Wildcard DNS may not be configured correctly." msgstr "" -#: vendor/wp-cli/wp-cli/php/commands/cron.php:424 +#: vendor/wp-cli/wp-cli/php/commands/cron-event.php:424 msgid "%s year" msgid_plural "%s years" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/cron.php:425 +#: vendor/wp-cli/wp-cli/php/commands/cron-event.php:425 msgid "%s month" msgid_plural "%s months" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/cron.php:426 +#: vendor/wp-cli/wp-cli/php/commands/cron-event.php:426 msgid "%s week" msgid_plural "%s weeks" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/cron.php:427 +#: vendor/wp-cli/wp-cli/php/commands/cron-event.php:427 msgid "%s day" msgid_plural "%s days" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/cron.php:428 +#: vendor/wp-cli/wp-cli/php/commands/cron-event.php:428 msgid "%s hour" msgid_plural "%s hours" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/cron.php:429 +#: vendor/wp-cli/wp-cli/php/commands/cron-event.php:429 msgid "%s minute" msgid_plural "%s minutes" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/cron.php:430 +#: vendor/wp-cli/wp-cli/php/commands/cron-event.php:430 msgid "%s second" msgid_plural "%s seconds" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/export.php:296 +#: vendor/wp-cli/wp-cli/php/commands/export.php:300 msgid "Invalid start ID: %d" msgstr "" -#: vendor/wp-cli/wp-cli/php/commands/media.php:90 +#: vendor/wp-cli/wp-cli/php/commands/media.php:100 msgid "image" msgid_plural "images" msgstr[0] "" msgstr[1] "" -#: vendor/wp-cli/wp-cli/php/commands/media.php:100 -msgid "An error occurred with image regeneration." -msgid_plural "An error occurred regenerating one or more images." -msgstr[0] "" -msgstr[1] "" - -#: vendor/wp-cli/wp-cli/php/commands/media.php:104 -msgid "the image" -msgid_plural "all images" -msgstr[0] "" -msgstr[1] "" - #: vendor/wp-cli/wp-cli/php/export/class-wp-export-query.php:327 msgid "Term is missing a parent: %s (%d)" msgstr "" @@ -20946,11 +20944,11 @@ msgstr "" msgid "WP Export: error writing to export file." msgstr "" -#: vendor/wp-cli/wp-cli/php/utils-wp.php:140 +#: vendor/wp-cli/wp-cli/php/utils-wp.php:150 msgid "Inactive Widgets" msgstr "" -#: vendor/wp-cli/wp-cli/php/utils-wp.php:143 +#: vendor/wp-cli/wp-cli/php/utils-wp.php:153 msgid "Drag widgets here to remove them from the sidebar but keep their settings." msgstr "" @@ -21574,8 +21572,8 @@ msgctxt "submit button" msgid "Search" msgstr "" -#: vendor/wp-cli/wp-cli/php/commands/core.php:616 -#: vendor/wp-cli/wp-cli/php/commands/core.php:679 +#: vendor/wp-cli/wp-cli/php/commands/core.php:542 +#: vendor/wp-cli/wp-cli/php/commands/core.php:609 msgctxt "Default network name" msgid "%s Sites" msgstr "" diff --git a/readme.txt b/readme.txt index b3c502b00f..fe2da1cb9e 100644 --- a/readme.txt +++ b/readme.txt @@ -16,13 +16,13 @@ ElasticPress, a fast and flexible search and query engine for WordPress, enables Here is a list of the amazing ElasticPress features included in the plugin: -__Search__: Beef up your search to be more accurate, search tags, categories, and other taxonomies, catch misspellings, weight content by recency and more. +__Search__: Instantly find the content you’re looking for. The first time. -__WooCommerce__: Allow customers to filter through products faster and improve product search relevancy. Enable editors to find orders and products more effectively in the admin. This feature will increase your sales bottom line and reduce administrative costs. +__WooCommerce__: “I want a cotton, woman’s t-shirt, for under $15 that’s in stock.” Faceted product browsing strains servers and increases load times. Your buyers can find the perfect product quickly, and buy it quickly. -__Related Posts__: Help users easily find related content by adding related posts to the end of each post. +__Related Posts__: ElasticPress understands data in real time, so it can instantly deliver engaging and precise related content with no impact on site performance. -__Protected Content__: Help editors more effectively browse through content. Load long lists of posts faster. Filter posts faster. Please note this syncs draft content to Elasticsearch. Enabling this feature will allow other ElasticPress features to work within the admin (i.e. WooCommerce and Search). You'll need to make sure your Elasticsearch instance is properly secured. +__Protected Content__: Optionally index all of your content, including private and unpublished content, to speed up searches and queries in places like the administrative dashboard. Please refer to [Github](https://github.com/10up/ElasticPress) for detailed usage instructions and documentation. From 28d19d064becc7257728e17f1f2c5fe7701e4ffa Mon Sep 17 00:00:00 2001 From: Ivan Kristianto Date: Tue, 28 Feb 2017 18:58:28 +0700 Subject: [PATCH 155/159] Fix woocommerce product search default order --- features/woocommerce/woocommerce.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/features/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php index ca673140d7..daadbd1307 100644 --- a/features/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -423,6 +423,15 @@ function ep_wc_translate_args( $query ) { $query->set( 'orderby', ep_wc_get_orderby_meta_mapping( 'menu_order' ) ); // Order by menu and title. } } + + /** + * Default order when doing search in woocommerce is 'ASC' + * These lines will change it to 'DESC' as we want to most relevant result + */ + if ( empty( $_GET['orderby'] ) && ! empty( $_GET['s'] ) && $query->is_main_query() ) { + $query->set( 'order', 'DESC' ); + } + } } From 216116d7677f7cd4be5088844810896b41b7106d Mon Sep 17 00:00:00 2001 From: Ivan Kristianto Date: Tue, 28 Feb 2017 23:47:45 +0700 Subject: [PATCH 156/159] Move the code for better readability --- features/woocommerce/woocommerce.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/features/woocommerce/woocommerce.php b/features/woocommerce/woocommerce.php index daadbd1307..fefcebff42 100644 --- a/features/woocommerce/woocommerce.php +++ b/features/woocommerce/woocommerce.php @@ -355,6 +355,14 @@ function ep_wc_translate_args( $query ) { if ( ! empty( $s ) ) { $query->set( 'orderby', false ); // Just order by relevance. + /** + * Default order when doing search in Woocommerce is 'ASC' + * These lines will change it to 'DESC' as we want to most relevant result + */ + if ( empty( $_GET['orderby'] ) && $query->is_main_query() ) { + $query->set( 'order', 'DESC' ); + } + // Search query if ( 'shop_order' === $post_type ) { $search_fields = $query->get( 'search_fields', array( 'post_title', 'post_content', 'post_excerpt' ) ); @@ -424,14 +432,6 @@ function ep_wc_translate_args( $query ) { } } - /** - * Default order when doing search in woocommerce is 'ASC' - * These lines will change it to 'DESC' as we want to most relevant result - */ - if ( empty( $_GET['orderby'] ) && ! empty( $_GET['s'] ) && $query->is_main_query() ) { - $query->set( 'order', 'DESC' ); - } - } } From 5b4cc69fbd45ff91b75c5310f04a2929aaa24eed Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Tue, 28 Feb 2017 13:42:44 -0500 Subject: [PATCH 157/159] Update readme for bugfix --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index fe2da1cb9e..d22c105b2d 100644 --- a/readme.txt +++ b/readme.txt @@ -64,6 +64,7 @@ We've bumped the minimum Elasticsearch version to 1.7 (although we strongly reco * Update ep_delete_post, include $post_type argument. Props (Ritesh-patel)[https://github.com/Ritesh-patel] * Fix post_type product getting set in any WP_Query if tax_query is provided in WooCommerce feature. Props (Ritesh-patel)[https://github.com/Ritesh-patel] * Adds 'number' param to satisfy WP v4.6+ fixing get_sites call. Props (rveitch)[https://github.com/rveitch] +* Order by proper relevancy in WooCommerce product search. Props (ivankristianto)[https://github.com/ivankristianto] = 2.1.2 (Requires re-index) = From 081a93e28fa32c76d4b47dc6af2713aed80669c2 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Tue, 28 Feb 2017 14:25:59 -0500 Subject: [PATCH 158/159] Turn off oembed discovery during prepare_post. Closes #735 --- classes/class-ep-api.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/classes/class-ep-api.php b/classes/class-ep-api.php index 53c68435be..a52e5b639d 100644 --- a/classes/class-ep-api.php +++ b/classes/class-ep-api.php @@ -568,6 +568,9 @@ public function prepare_post( $post_id ) { } } + // Turn off oEmbed auto discovery as this will create an error while indexing + add_filter( 'embed_oembed_discover', '__return_false' ); + $post_args = array( 'post_id' => $post_id, 'ID' => $post_id, @@ -605,6 +608,9 @@ public function prepare_post( $post_id ) { $post_args = apply_filters( 'ep_post_sync_args_post_prepare_meta', $post_args, $post_id ); + // Turn back on oEmbed discovery + remove_filter( 'embed_oembed_discover', '__return_false' ); + return $post_args; } From b96b9e98bf6a49a378e7c79c17daec32bd749544 Mon Sep 17 00:00:00 2001 From: Taylor Lovett Date: Tue, 28 Feb 2017 14:26:54 -0500 Subject: [PATCH 159/159] Update readme for oembed fix --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index d22c105b2d..8adcdadb7b 100644 --- a/readme.txt +++ b/readme.txt @@ -65,6 +65,7 @@ We've bumped the minimum Elasticsearch version to 1.7 (although we strongly reco * Fix post_type product getting set in any WP_Query if tax_query is provided in WooCommerce feature. Props (Ritesh-patel)[https://github.com/Ritesh-patel] * Adds 'number' param to satisfy WP v4.6+ fixing get_sites call. Props (rveitch)[https://github.com/rveitch] * Order by proper relevancy in WooCommerce product search. Props (ivankristianto)[https://github.com/ivankristianto] +* Fix recursion fatal error due to oembed discovery during syncing. Props (ivankristianto)[https://github.com/ivankristianto] = 2.1.2 (Requires re-index) =