[ label, url, screen ID ].
*/
public static function tabs() : array {
$tabs = [
'pages' => [
'label' => __( 'Pages', 'deadbolt' ),
'url' => Pages::url(),
'screen' => self::pages_screen_id(),
],
'templates' => [
'label' => __( 'Templates', 'deadbolt' ),
'url' => admin_url( 'edit.php?post_type=' . Templates::POST_TYPE ),
'screen' => 'edit-' . Templates::POST_TYPE,
],
'settings' => [
'label' => __( 'Settings', 'deadbolt' ),
'url' => Settings::url(),
'screen' => self::settings_screen_id(),
],
];
return apply_filters( 'deadbolt_tabs', $tabs );
} // End tabs()
/**
* Register the top-level menu and the Pages tab. Runs before post type submenus so Pages is the first item.
*
* @return void
*/
public function register_menu() : void {
$title = __( 'Deadbolt', 'deadbolt' );
add_menu_page( $title, $title, 'manage_options', self::SLUG, [ Pages::class, 'render_page' ], 'dashicons-visibility', 81 );
add_submenu_page( self::SLUG, __( 'Pages', 'deadbolt' ), __( 'Pages', 'deadbolt' ), 'manage_options', self::SLUG, [ Pages::class, 'render_page' ] );
} // End register_menu()
/**
* Register the Settings tab. Runs after post type submenus so Settings comes after Templates.
*
* @return void
*/
public function register_settings_menu() : void {
self::$settings_hook = (string) add_submenu_page( self::SLUG, __( 'Settings', 'deadbolt' ), __( 'Settings', 'deadbolt' ), 'manage_options', self::SETTINGS_SLUG, [ Settings::class, 'render_page' ] );
} // End register_settings_menu()
/**
* Get the active tab key.
*
* @return string
*/
public static function active_tab() : string {
$screen = get_current_screen();
foreach ( self::tabs() as $key => $tab ) {
if ( $screen && $screen->id === $tab[ 'screen' ] ) {
return $key;
}
}
return '';
} // End active_tab()
/**
* Add a body class on the plugin's screens.
*
* @param string $classes
* @return string
*/
public function body_class( $classes ) : string {
return Helpers::is_plugin_screen() ? $classes . ' deadbolt-admin-screen' : (string) $classes;
} // End body_class()
/**
* Remove Screen Options and Help from the plugin's screens.
*
* @return void
*/
public function remove_screen_meta() : void {
if ( ! Helpers::is_plugin_screen() ) {
return;
}
add_filter( 'screen_options_show_screen', '__return_false' );
get_current_screen()->remove_help_tabs();
} // End remove_screen_meta()
/**
* Render the header and subheader before admin notices, on the plugin's screens only.
*
* @return void
*/
public function maybe_render_header() : void {
if ( ! Helpers::is_plugin_screen() ) {
return;
}
$tabs = self::tabs();
$active = self::active_tab();
$logo = self::logo_url();
$logo_info = self::logo_info( $logo );
?>
$value ) {
$channel = $value / 255;
$total += $weights[ $index ] * ( $channel <= 0.03928 ? $channel / 12.92 : ( ( $channel + 0.055 ) / 1.055 ) ** 2.4 );
}
return $total;
} // End luminance()
/**
* Get the header logo URL.
*
* @return string
*/
private static function logo_url() : string {
return (string) apply_filters( 'deadbolt_logo', add_query_arg( 'ver', Bootstrap::script_version(), Bootstrap::url() . 'inc/img/logo.png' ) );
} // End logo_url()
/**
* Get the width, height and average luminance of a local logo image. Cached until the file changes.
*
* Luminance is weighted by opacity. Values are null when the logo cannot be read.
*
* @param string $url
* @return array [ width, height, lum ]
*/
private static function logo_info( string $url ) : array {
$info = [ 'width' => null, 'height' => null, 'lum' => null, 'data' => '' ];
$uploads = wp_upload_dir( null, false );
$path = '';
if ( 0 === strpos( $url, Bootstrap::url() ) ) {
$path = Bootstrap::dir() . ltrim( substr( strtok( $url, '?' ), strlen( Bootstrap::url() ) ), '/' );
} elseif ( 0 === strpos( $url, $uploads[ 'baseurl' ] ) ) {
$path = $uploads[ 'basedir' ] . substr( strtok( $url, '?' ), strlen( $uploads[ 'baseurl' ] ) );
}
if ( '' === $path || ! is_readable( $path ) ) {
return $info;
}
$key = md5( '2|' . $path . filemtime( $path ) );
$cached = get_transient( 'deadbolt_logo_info' );
if ( is_array( $cached ) && ( $cached[ 'key' ] ?? '' ) === $key ) {
return array_merge( $info, (array) $cached[ 'info' ] );
}
$type = wp_check_filetype( $path )[ 'type' ];
$raw = filesize( $path ) <= 24 * KB_IN_BYTES ? file_get_contents( $path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reads a local file.
if ( $raw && $type && 0 === strpos( $type, 'image/' ) ) {
$info[ 'data' ] = 'data:' . $type . ';base64,' . base64_encode( $raw ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- embeds a small local image.
}
$size = wp_getimagesize( $path );
if ( $size ) {
$info[ 'width' ] = (int) $size[ 0 ];
$info[ 'height' ] = (int) $size[ 1 ];
}
$data = filesize( $path ) <= 2 * MB_IN_BYTES && function_exists( 'imagecreatefromstring' ) ? file_get_contents( $path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reads a local file.
$source = $data ? @imagecreatefromstring( $data ) : false; // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- unsupported image data returns false.
if ( $source ) {
$sample = 32;
$small = imagecreatetruecolor( $sample, $sample );
imagealphablending( $small, false );
imagesavealpha( $small, true );
imagefill( $small, 0, 0, imagecolorallocatealpha( $small, 0, 0, 0, 127 ) );
imagecopyresampled( $small, $source, 0, 0, 0, 0, $sample, $sample, imagesx( $source ), imagesy( $source ) );
$total = 0.0;
$weight = 0.0;
for ( $y = 0; $y < $sample; $y++ ) {
for ( $x = 0; $x < $sample; $x++ ) {
$rgba = imagecolorat( $small, $x, $y );
$alpha = 1 - ( ( $rgba >> 24 ) & 127 ) / 127;
$total += self::luminance( ( $rgba >> 16 ) & 255, ( $rgba >> 8 ) & 255, $rgba & 255 ) * $alpha;
$weight += $alpha;
}
}
$info[ 'lum' ] = $weight > 0 ? $total / $weight : null;
}
set_transient( 'deadbolt_logo_info', [ 'key' => $key, 'info' => $info ], MONTH_IN_SECONDS );
return $info;
} // End logo_info()
/**
* Check whether the logo has too little contrast against the header and should be inverted.
*
* @param string $url
* @param float|null $lum
* @return bool
*/
private static function logo_needs_invert( string $url, ?float $lum ) : bool {
if ( null === $lum ) {
return (bool) apply_filters( 'deadbolt_invert_logo', false, $url );
}
$hex = ltrim( self::header_color(), '#' );
$hex = 3 === strlen( $hex ) ? preg_replace( '/(.)/', '$1$1', $hex ) : $hex;
$bg = self::luminance( (int) hexdec( substr( $hex, 0, 2 ) ), (int) hexdec( substr( $hex, 2, 2 ) ), (int) hexdec( substr( $hex, 4, 2 ) ) );
$ratio = ( max( $lum, $bg ) + 0.05 ) / ( min( $lum, $bg ) + 0.05 );
return (bool) apply_filters( 'deadbolt_invert_logo', $ratio < 3, $url );
} // End logo_needs_invert()
/**
* Preload the logo so it is ready when the header renders.
*
* @return void
*/
public function preload_logo() : void {
$logo = self::logo_url();
if ( ! Helpers::is_plugin_screen() || self::logo_info( $logo )[ 'data' ] ) {
return;
}
?>