';
}
return $out . '';
}
/**
* The assessment as separate parts, so a screen can put actions between them.
*
* @param array $a Output of ShouldCheck_Assessment::build().
* @return array{verdict:string,findings:string,rest:string}
*/
public static function parts( array $a ) {
$findings = '
';
}
$auto = count( $plan ) - count( $manual );
return self::group( 'shouldcheck-g-plan', 'info', 'What ShouldCheck did (' . $auto . ') and what it left to you (' . count( $manual ) . ')', $inner, false );
}
/**
* Singular of a findings label.
*
* @param string $label Plural label.
* @return string
*/
private static function singular( $label ) {
$map = array(
'REST routes' => 'REST route',
'Logged-out actions' => 'Logged-out action',
'WooCommerce callbacks' => 'WooCommerce callback',
);
return $map[ $label ] ?? $label;
}
/**
* What ShouldCheck read, as a one-line strip.
*
* @param array $read Rows of n and label.
* @return string
*/
private static function proof_strip( array $read ) {
$files = 0;
$plugins = 0;
foreach ( $read as $r ) {
if ( false !== strpos( $r['label'], 'PHP file' ) ) {
$files = (int) $r['n'];
}
if ( false !== strpos( $r['label'], 'plugin' ) ) {
$plugins = (int) $r['n'];
}
}
if ( ! $files ) {
return '';
}
$txt = 'ShouldCheck read ' . $files . ' PHP file' . ( 1 === $files ? '' : 's' );
if ( $plugins ) {
$txt .= ' and compared them with ' . $plugins . ' of your plugin' . ( 1 === $plugins ? '' : 's' );
}
return '
' . esc_html( $txt ) . '.
';
}
/**
* Four result tiles with a status pill. The pill carries a word, not only a colour.
*
* @param array $tiles Tiles from the assessment.
* @return string
*/
private static function tiles( array $tiles ) {
if ( ! $tiles ) {
return '';
}
$icon = array(
'blocking' => 'blocking',
'verify' => 'verify',
'passed' => 'passed',
'unchecked' => 'unchecked',
'none' => 'info',
);
$out = '
';
if ( ! empty( $t['needs'] ) ) {
$out .= 'See what to do';
} elseif ( ! empty( $t['unchecked'] ) ) {
$out .= 'See why';
} elseif ( ! empty( $t['passed'] ) ) {
$out .= 'See what we checked';
} elseif ( ! empty( $t['notes'] ) ) {
$out .= 'See what to know';
}
$out .= '
';
}
return $out . '
';
}
/**
* Everything that needs a look, grouped by topic instead of one long list.
*
* @param array $blocking Blocking items.
* @param array $verify Items to check.
* @return string
*/
private static function grouped_findings( array $blocking, array $verify ) {
if ( ! $blocking && ! $verify ) {
return '';
}
$topics = array(
'compat' => array( 'Compatibility', 'Does it fit your WordPress, PHP and WooCommerce?' ),
'conflicts' => array( 'Possible clashes', 'Could it fight with a plugin you already use?' ),
'exposure' => array( 'Outside access', 'Which doors does it leave open to visitors and other servers?' ),
'checkout' => array( 'Checkout', 'Could it get in the way of orders and payments?' ),
);
$out = '
What to look at ' . ( count( $blocking ) + count( $verify ) ) . '
';
}
/**
* The text of one finding: the plain explanation with the technical detail folded away,
* or the technical text alone when there is no explanation.
*
* @param array $it Item.
* @param string $label Label for the action when there is no explanation.
* @return string
*/
private static function finding_body( array $it, $label ) {
$say = (array) ( $it['say'] ?? array() );
$out = '';
if ( ! empty( $say['noticed'] ) ) {
$out .= '
and ' . (int) ( count( $k['items'] ) - 40 ) . ' more
';
}
$out .= '
';
}
$out .= '
A security signal is a permission, nonce or signature pattern near its own handler. It is a lead from reading the code, not proof that the entry point is protected.
' . esc_html( $cap['label'] ) . '';
if ( $cap['inferred'] ) {
$out .= ' Guessed from the listing\'s name and tags. The code was not read.';
}
$out .= '
ShouldCheck reads what a package declares and how its code is written. A passed check means the author said so, or a code pattern was found. It does not mean the plugin works.
';
return self::group( 'shouldcheck-g-limits', 'info', 'Not covered by ShouldCheck (' . count( $limits ) . ')', $inner, false );
}
/**
* Warning when the site has changed since a report was made.
*
* @param array $report Report.
* @return string
*/
public static function drift_notice( array $report ) {
if ( empty( $report['environment_hash'] ) ) {
return '';
}
if ( hash_equals( (string) $report['environment_hash'], ShouldCheck_Site::fingerprint( ShouldCheck_Site::snapshot() ) ) ) {
return '';
}
return '
This review is out of date. The site has changed since it was made. Review the plugin again before installing.
ShouldCheck records changes WordPress reports and differences found when it compares the site with its last snapshot. Time is when ShouldCheck noticed, which can be later than the change. It does not record file edits or errors.
';
}
/**
* " Your quietest hours are ..." for the money-path line, or ''.
*
* @return string
*/
private static function quiet_sentence() {
$q = ShouldCheck_Money_Path::quiet_hours();
if ( empty( $q['ok'] ) ) {
return '';
}
return ' ' . sprintf( 'Your quietest three hours are %1$s to %2$s (%3$s), from %4$d orders over %5$d days.', sprintf( '%02d:00', $q['start'] ), sprintf( '%02d:00', $q['end'] ), wp_timezone_string(), (int) $q['orders'], (int) $q['days'] );
}
/**
* Yes / no / unknown with a status icon where something needs a look.
*
* @param bool|null $v Value.
* @param string $yes Text for true.
* @param string $no Text for false.
* @param bool $look Which value needs a look: true, false, or null for none.
* @return string
*/
private static function fact( $v, $yes, $no, $look = null ) {
if ( null === $v ) {
return 'Not shown in settings';
}
$text = $v ? $yes : $no;
$icon = ( null !== $look && $v === $look ) ? self::icon( 'verify' ) . ' ' : '';
return $icon . esc_html( $text );
}
/**
* What the store's checkout looks like from its settings. Nothing here is a test.
*
* @return string Empty when WooCommerce is not active.
*/
public static function checkout_card() {
$gateways = ShouldCheck_Money_Path::gateways();
if ( null === $gateways ) {
return '';
}
$site = ShouldCheck_Site::snapshot();
$setup = (array) ( $site['woo_setup'] ?? array() );
$out = '
Your checkout
';
$out .= '
Read from your settings. ShouldCheck did not place an order or take a payment, so none of this shows that checkout works.
' . ( $blocks_page ? self::fact( $g['blocks'], 'Registered', 'Not registered', false ) : 'Not used on your checkout page' ) . '
';
}
$out .= '
Credentials show only whether a value was entered, never the value. "Not shown in settings" means this plugin keeps the setting somewhere ShouldCheck does not read.
Open checkouts right now: ' . (int) $open . ' started in the last 15 minutes and not yet paid.
';
return $out . '';
}
/**
* The one entry button ShouldCheck adds to WordPress screens. Always reads "ShouldCheck".
*
* @param string $href Escaped URL.
* @param string $label Accessible name that says what it does.
* @param bool $primary Whether it is the main button on the card.
* @param string $size Optional size class, such as button-small.
*
* @return string
*/
public static function button( $href, $label, $primary = false, $size = '' ) {
return 'ShouldCheck';
}
/**
* A message from WordPress as plain text. WordPress errors often carry HTML, such as a link to the support forums.
* Shown escaped, that HTML would print as raw tags. The words are kept and the markup is dropped.
*
* @param string $message Message that may contain HTML.
* @return string
*/
public static function plain( $message ) {
return trim( preg_replace( '/\s+/', ' ', wp_strip_all_tags( (string) $message ) ), " \n\r\t\v\x00" );
}
/**
* One choice shown as a card. The whole card is the radio button.
*
* @param string $name Field name.
* @param string $value Field value.
* @param bool $checked Whether it is selected.
* @param string $title Title.
* @param string $text One-line description.
* @param string $preview Small picture of the result (trusted markup built here).
* @param string $badge Optional badge text.
* @return string
*/
private static function choice( $name, $value, $checked, $title, $text, $preview, $badge = '' ) {
return '';
}
/**
* Small picture of a button, for the choice cards.
*
* @param string $label Text.
* @param bool $on Whether it is the ShouldCheck button.
* @return string
*/
private static function mini( $label, $on = false ) {
return '' . ( $on ? '' : '' ) . esc_html( $label ) . '';
}
/**
* Settings card. Before the first choice it is a short setup.
*
* @param array $s Settings.
* @return string
*/
public static function settings_card( array $s ) {
$setup = empty( $s['onboarded'] );
$mode = ( $setup || ! empty( $s['beside'] ) ) ? 'beside' : 'manual';
$out = '
Uploaded ZIPs are always read on your site and need no download. What can leave your site: requests to api.wordpress.org for plugin listings, and, if you allow it, to downloads.wordpress.org for the ZIP. ShouldCheck sends nothing to its authors, has no account and runs no tracking. Installs started elsewhere (inside WooCommerce, by FTP or WP-CLI) are not covered.
';
return $out;
}
/**
* Shown after a reinstall that found data kept from before.
*
* @param array $kept Counts and date written at uninstall.
* @return string
*/
public static function welcome_back_card( array $kept ) {
$when = ! empty( $kept['at'] ) ? gmdate( 'j M Y', (int) strtotime( (string) $kept['at'] ) ) : '';
$r = (int) ( $kept['reviews'] ?? 0 );
$e = (int) ( $kept['events'] ?? 0 );
$set = (bool) ShouldCheck_Settings::get( 'onboarded' );
$out = '
Welcome back
';
$out .= '
ShouldCheck found what it kept when it was removed' . ( '' !== $when ? ' on ' . esc_html( $when ) : '' ) . ': ' . esc_html( (string) $r ) . ' review' . ( 1 === $r ? '' : 's' ) . ', ' . esc_html( (string) $e ) . ' site change' . ( 1 === $e ? '' : 's' ) . '' . ( $set ? ' and your settings' : '' ) . '.
';
$out .= '';
$out .= '
' . ( $set ? 'Keeping them restores your reviews and history as they were. Your settings are back as you left them, so setup is not asked again. Deleting them starts fresh, settings included.' : 'Keeping them restores your reviews and history as they were. Deleting them starts fresh.' ) . '