http_check( 'home', __( 'Homepage responds', '7labs-debug-branding-for-commerce7' ), home_url( '/' ), array( 200, 301, 302 ) ); $items[] = $this->http_check( 'collection_root', __( 'Base /collection responds (should redirect or render)', '7labs-debug-branding-for-commerce7' ), home_url( '/collection/' ), array( 200, 301, 302 ), ! empty( $settings['redirect_collection_root'] ) ); if ( $slug ) { $shop = home_url( '/collection/' . $slug . '/' ); $items[] = $this->http_check( 'shop', __( 'Default shop collection URL', '7labs-debug-branding-for-commerce7' ), $shop, array( 200 ) ); $items[] = $this->widget_mount_check( 'shop_widget', __( 'Shop page includes Commerce7 mount point', '7labs-debug-branding-for-commerce7' ), $shop ); } else { $items[] = array( 'id' => 'shop', 'label' => __( 'Default shop collection URL', '7labs-debug-branding-for-commerce7' ), 'url' => '', 'status' => 'skipped', 'code' => null, 'detail' => __( 'Set a collection slug first.', '7labs-debug-branding-for-commerce7' ), 'pass' => false, ); } foreach ( array( 'cart', 'checkout', 'product' ) as $route ) { $items[] = $this->http_check( $route, sprintf( /* translators: %s: route */ __( '/%s route responds (optional)', '7labs-debug-branding-for-commerce7' ), $route ), home_url( '/' . $route . '/' ), array( 200, 301, 302 ), false, true ); } $items[] = $this->widget_mount_check( 'checkout_widget', __( 'Checkout includes Commerce7 mount point', '7labs-debug-branding-for-commerce7' ), home_url( '/checkout/' ) ); // Redirect behavior: collection root should not stay empty if redirect enabled. if ( ! empty( $settings['redirect_collection_root'] ) && $slug ) { $items[] = $this->redirect_target_check( $slug ); } $passed = $failed = $skipped = 0; foreach ( $items as $item ) { if ( 'skipped' === $item['status'] ) { ++$skipped; } elseif ( ! empty( $item['pass'] ) ) { ++$passed; } else { ++$failed; } } $report = array( 'passed' => $passed, 'failed' => $failed, 'skipped' => $skipped, 'items' => $items, 'ran_at' => current_time( 'mysql' ), ); set_transient( 'c7dbt_last_qa', $report, WEEK_IN_SECONDS ); return $report; } /** * Last cached report. * * @return array|null */ public function last_report() { $report = get_transient( 'c7dbt_last_qa' ); return is_array( $report ) ? $report : null; } /** * HTTP status check. * * @param string $id ID. * @param string $label Label. * @param string $url URL. * @param array $ok_codes OK codes. * @param bool $prefer_redirect Prefer documenting redirect Location when true. * @return array */ private function http_check( $id, $label, $url, array $ok_codes, $prefer_redirect = false, $optional_404 = false ) { $response = $this->request( $url ); if ( is_wp_error( $response ) ) { $msg = $response->get_error_message(); // Common on local / Docker / hosts that block loopback HTTP. $loopback = (bool) preg_match( '/cURL error 7|Failed to connect|Connection refused|timed out|SSL/i', $msg ); return array( 'id' => $id, 'label' => $label, 'url' => $url, 'status' => $loopback ? 'skipped' : 'fail', 'code' => null, 'detail' => $loopback ? sprintf( /* translators: %s: error */ __( 'Skipped (server cannot reach itself): %s', '7labs-debug-branding-for-commerce7' ), $msg ) : $msg, 'pass' => false, ); } $code = (int) wp_remote_retrieve_response_code( $response ); $pass = in_array( $code, $ok_codes, true ); $detail = sprintf( /* translators: %d: HTTP code */ __( 'HTTP %d', '7labs-debug-branding-for-commerce7' ), $code ); // Optional routes (e.g. cart) are frequently rendered by Commerce7 as a // widget/flyout rather than a standalone page, so a 404 is expected and // not a failure. Report it as skipped/informational instead. if ( ! $pass && $optional_404 && 404 === $code ) { return array( 'id' => $id, 'label' => $label, 'url' => $url, 'status' => 'skipped', 'code' => $code, 'detail' => __( 'Optional — not a standalone page (Commerce7 usually renders this inline or as a widget). Safe to ignore if this feature works on your site.', '7labs-debug-branding-for-commerce7' ), 'pass' => false, ); } if ( $prefer_redirect && $code >= 300 && $code < 400 ) { $loc = wp_remote_retrieve_header( $response, 'location' ); if ( $loc ) { $detail .= ' → ' . $loc; } } return array( 'id' => $id, 'label' => $label, 'url' => $url, 'status' => $pass ? 'pass' : 'fail', 'code' => $code, 'detail' => $detail, 'pass' => $pass, ); } /** * Loopback-aware HTTP GET (retry without SSL verify once). * * @param string $url URL. * @return array|WP_Error */ private function request( $url ) { $args = array( 'timeout' => 12, 'redirection' => 0, 'sslverify' => apply_filters( 'c7dbt_qa_sslverify', true ), 'headers' => array( 'User-Agent' => 'C7DBT-SiteQA/' . C7DBT_VERSION . '; ' . home_url( '/' ), ), ); $response = wp_remote_get( $url, $args ); if ( ! is_wp_error( $response ) ) { return $response; } // Retry once with sslverify off — common local / self-signed setups. $args['sslverify'] = false; $retry = wp_remote_get( $url, $args ); return is_wp_error( $retry ) ? $response : $retry; } /** * Fetch HTML and confirm Commerce7 widget markers exist. * * @param string $id ID. * @param string $label Label. * @param string $url URL. * @return array */ private function widget_mount_check( $id, $label, $url ) { $response = $this->request_body( $url ); if ( is_wp_error( $response ) ) { $msg = $response->get_error_message(); $loopback = (bool) preg_match( '/cURL error 7|Failed to connect|Connection refused|timed out|SSL/i', $msg ); return array( 'id' => $id, 'label' => $label, 'url' => $url, 'status' => $loopback ? 'skipped' : 'fail', 'code' => null, 'detail' => $loopback ? sprintf( /* translators: %s: error */ __( 'Skipped (server cannot reach itself): %s', '7labs-debug-branding-for-commerce7' ), $msg ) : $msg, 'pass' => false, 'needs_dev' => ! $loopback, ); } $code = (int) wp_remote_retrieve_response_code( $response ); if ( $code < 200 || $code >= 400 ) { return array( 'id' => $id, 'label' => $label, 'url' => $url, 'status' => 'fail', 'code' => $code, 'detail' => sprintf( /* translators: %d: HTTP code */ __( 'Could not read page HTML (HTTP %d) — fix the route first.', '7labs-debug-branding-for-commerce7' ), $code ), 'pass' => false, 'needs_dev' => true, ); } $body = (string) wp_remote_retrieve_body( $response ); $found = $this->body_has_c7_mount( $body ); return array( 'id' => $id, 'label' => $label, 'url' => $url, 'status' => $found ? 'pass' : 'fail', 'code' => $code, 'detail' => $found ? __( 'Commerce7 mount markers found in HTML.', '7labs-debug-branding-for-commerce7' ) : __( 'Page loaded but no Commerce7 mount point detected — common with Elementor canvas or missing default content block.', '7labs-debug-branding-for-commerce7' ), 'pass' => $found, 'needs_dev' => ! $found, ); } /** * HTTP GET with limited redirect follow for HTML body checks. * * @param string $url URL. * @return array|WP_Error */ private function request_body( $url ) { $args = array( 'timeout' => 15, 'redirection' => 3, 'sslverify' => apply_filters( 'c7dbt_qa_sslverify', true ), 'headers' => array( 'User-Agent' => 'C7DBT-SiteQA/' . C7DBT_VERSION . '; ' . home_url( '/' ), ), ); $response = wp_remote_get( $url, $args ); if ( ! is_wp_error( $response ) ) { return $response; } $args['sslverify'] = false; $retry = wp_remote_get( $url, $args ); return is_wp_error( $retry ) ? $response : $retry; } /** * Detect common Commerce7 widget mount markers in HTML. * * @param string $html HTML body. * @return bool */ private function body_has_c7_mount( $html ) { if ( ! is_string( $html ) || '' === $html ) { return false; } $patterns = array( '/id=["\']c7-content["\']/i', '/class=["\'][^"\']*c7-content/i', '/c7-content-container/i', '/data-c7[\w-]*/i', '/commerce7\.com/i', '/c7-app/i', ); foreach ( $patterns as $pattern ) { if ( preg_match( $pattern, $html ) ) { return true; } } return false; } /** * Confirm /collection redirects toward default slug. * * @param string $slug Slug. * @return array */ private function redirect_target_check( $slug ) { $url = home_url( '/collection/' ); $response = $this->request( $url ); if ( is_wp_error( $response ) ) { $msg = $response->get_error_message(); $loopback = (bool) preg_match( '/cURL error 7|Failed to connect|Connection refused|timed out|SSL/i', $msg ); return array( 'id' => 'redirect_target', 'label' => __( '/collection redirect target', '7labs-debug-branding-for-commerce7' ), 'url' => $url, 'status' => $loopback ? 'skipped' : 'fail', 'code' => null, 'detail' => $loopback ? sprintf( /* translators: %s: error */ __( 'Skipped (server cannot reach itself): %s', '7labs-debug-branding-for-commerce7' ), $msg ) : $msg, 'pass' => false, ); } $code = (int) wp_remote_retrieve_response_code( $response ); $loc = (string) wp_remote_retrieve_header( $response, 'location' ); $ok = ( $code >= 300 && $code < 400 && false !== strpos( $loc, '/collection/' . $slug ) ); return array( 'id' => 'redirect_target', 'label' => __( '/collection redirect target', '7labs-debug-branding-for-commerce7' ), 'url' => $url, 'status' => $ok ? 'pass' : 'fail', 'code' => $code, 'detail' => $ok ? sprintf( /* translators: %s: location */ __( 'Redirects to %s', '7labs-debug-branding-for-commerce7' ), $loc ) : sprintf( /* translators: 1: code, 2: location */ __( 'Expected redirect to /collection/%1$s — got HTTP %2$d (%3$s)', '7labs-debug-branding-for-commerce7' ), $slug, $code, $loc ? $loc : __( 'no Location header', '7labs-debug-branding-for-commerce7' ) ), 'pass' => $ok, ); } /** * Dashboard status. * * @return array */ public function status() { $last = $this->last_report(); if ( ! $last ) { return array( 'status' => 'warn', 'label' => __( 'Site QA not run yet', '7labs-debug-branding-for-commerce7' ), 'detail' => __( 'Run live HTTP checks against your shop URLs.', '7labs-debug-branding-for-commerce7' ), ); } if ( $last['failed'] > 0 ) { return array( 'status' => 'error', 'label' => __( 'Site QA found failures', '7labs-debug-branding-for-commerce7' ), 'detail' => sprintf( /* translators: %d: failures */ _n( '%d HTTP check failed.', '%d HTTP checks failed.', $last['failed'], '7labs-debug-branding-for-commerce7' ), $last['failed'] ), 'count' => $last['failed'], ); } return array( 'status' => 'ok', 'label' => __( 'Site QA passed', '7labs-debug-branding-for-commerce7' ), 'detail' => sprintf( /* translators: 1: passed, 2: time */ __( '%1$d checks passed (last run %2$s).', '7labs-debug-branding-for-commerce7' ), $last['passed'], $last['ran_at'] ), ); } }