PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | class/class-mainwp-system-utility.php +314 -24 6.1.7 → 6.2.1 View file →
@@ -30,8 +30,19 @@
30 30 */
31 31 private static $instance = null;
32 32
33 33 /**
34 + * Request-local contrast results for tag backgrounds.
35 + *
36 + * Keyed by normalized #rrggbb. Invalid colors are not stored.
37 + *
38 + * @static
39 + *
40 + * @var array
41 + */
42 + private static $tag_contrast_cache = array();
43 +
44 + /**
34 45 * Method instance()
35 46 *
36 47 * Create a public static instance.
37 48 *
@@ -44,10 +55,37 @@
44 55 }
45 56 return static::$instance;
46 57 }
47 58
59 + /**
60 + * Constructor.
61 + *
62 + * Run each time the class is called.
63 + *
64 + * @return void
65 + */
66 + public function __construct() {
67 + add_action( 'admin_init', array( &$this, 'admin_init' ) );
68 + }
48 69
49 70 /**
71 + * Method admin_init().
72 + *
73 + * @return void
74 + */
75 + public function admin_init() {
76 + $current_month = gmdate( 'Y-m' );
77 + if ( get_option( 'mainwp_last_short_term_notice_purge' ) !== $current_month ) { // Run once per month.
78 + MainWP_Utility::purge_short_term_notices();
79 + update_option(
80 + 'mainwp_last_short_term_notice_purge',
81 + $current_month,
82 + false
83 + );
84 + }
85 + }
86 +
87 + /**
50 88 * Method get_class_name()
51 89 *
52 90 * Get Class Name.
53 91 *
@@ -727,22 +765,14 @@
727 765 $tagx = MainWP_DB_Common::instance()->get_group_by_name( $tag );
728 766 $tagc = is_object( $tagx ) && '' !== $tagx->color ? $tagx->color : '';
729 767 }
730 768
731 - if ( '' !== $tagc ) {
732 - $tag_a_style = 'style="color:#fff!important;opacity:1;"';
733 - $tag_style = 'style="background-color:' . esc_html( $tagc ) . '"';
734 - } else {
735 - $tag_a_style = '';
736 - $tag_style = '';
769 + $tag_id = '';
770 + if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
771 + $tag_id = $tags_ids[ $idx ];
737 772 }
738 773
739 - if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
740 - $tag_id = $tags_ids[ $idx ];
741 - $tags_labels .= '<span ' . $tag_style . ' tag_id="' . $tag_id . '" class="ui tag mini label"><a ' . $tag_a_style . ' href="' . esc_url( $href . $tag_id ) . '">' . esc_html( $tag ) . '</a></span>';
742 - } else {
743 - $tags_labels .= '<span ' . $tag_style . ' class="ui tag mini label">' . esc_html( $tag ) . '</span>';
744 - }
774 + $tags_labels .= self::get_site_tag_label( $tag, $tag_id, $href, $tagc );
745 775 }
746 776 }
747 777 }
748 778 return $tags_labels;
@@ -779,22 +809,14 @@
779 809 foreach ( $tags as $idx => $tag ) {
780 810 $tag = trim( $tag );
781 811 $tagc = $tags_colors[ $idx ];
782 812
783 - if ( '' !== $tagc ) {
784 - $tag_a_style = 'style="color:#fff!important;opacity:1;"';
785 - $tag_style = 'style="background-color:' . esc_html( $tagc ) . '"';
786 - } else {
787 - $tag_a_style = '';
788 - $tag_style = '';
813 + $tag_id = '';
814 + if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
815 + $tag_id = $tags_ids[ $idx ];
789 816 }
790 817
791 - if ( isset( $tags_ids[ $idx ] ) && ! empty( $tags_ids[ $idx ] ) ) {
792 - $tag_id = $tags_ids[ $idx ];
793 - $tags_labels .= '<span ' . $tag_style . ' tag_id="' . $tag_id . '" class="ui tag mini label"><a ' . $tag_a_style . ' href="' . esc_url( $href . $tag_id ) . '">' . esc_html( $tag ) . '</a></span>';
794 - } else {
795 - $tags_labels .= '<span ' . $tag_style . ' class="ui tag mini label">' . esc_html( $tag ) . '</span>';
796 - }
818 + $tags_labels .= self::get_site_tag_label( $tag, $tag_id, $href, $tagc );
797 819 }
798 820 }
799 821 }
800 822 return $tags_labels;
@@ -800,8 +822,133 @@
800 822 return $tags_labels;
801 823 }
802 824
803 825 /**
826 + * Method get_tag_contrast_color()
827 + *
828 + * Pick white or black text for a tag background.
829 + * White is returned when its contrast is at least 4.5:1.
830 + *
831 + * @param string $background Stored tag color.
832 + *
833 + * @return string #ffffff, #000000, or an empty string when the color is unusable.
834 + */
835 + public static function get_tag_contrast_color( $background ) {
836 + $normalized = self::normalize_tag_color( $background );
837 + if ( '' === $normalized ) {
838 + return '';
839 + }
840 +
841 + if ( isset( self::$tag_contrast_cache[ $normalized ] ) ) {
842 + return self::$tag_contrast_cache[ $normalized ];
843 + }
844 +
845 + $white_contrast = 1.05 / ( self::tag_relative_luminance( $normalized ) + 0.05 );
846 + if ( $white_contrast >= 4.5 ) {
847 + $foreground = '#ffffff';
848 + } else {
849 + $foreground = '#000000';
850 + }
851 +
852 + self::$tag_contrast_cache[ $normalized ] = $foreground;
853 + return $foreground;
854 + }
855 +
856 + /**
857 + * Method normalize_tag_color()
858 + *
859 + * Reduce a stored tag color to lowercase #rrggbb.
860 + *
861 + * @param string $background Stored tag color.
862 + *
863 + * @return string Normalized color, or an empty string when it is not 3 or 6 digit hex.
864 + */
865 + private static function normalize_tag_color( $background ) {
866 + if ( ! is_string( $background ) ) {
867 + return '';
868 + }
869 +
870 + $background = trim( $background );
871 + if ( ! preg_match( '/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/', $background ) ) {
872 + return '';
873 + }
874 +
875 + $background = strtolower( $background );
876 + if ( 4 === strlen( $background ) ) {
877 + $background = '#' . $background[1] . $background[1] . $background[2] . $background[2] . $background[3] . $background[3];
878 + }
879 +
880 + return $background;
881 + }
882 +
883 + /**
884 + * Method tag_relative_luminance()
885 + *
886 + * WCAG relative luminance for a normalized #rrggbb color.
887 + *
888 + * @param string $hex Normalized background.
889 + *
890 + * @return float Luminance between 0 and 1.
891 + */
892 + private static function tag_relative_luminance( $hex ) {
893 + $red = self::tag_channel_linear( hexdec( substr( $hex, 1, 2 ) ) / 255 );
894 + $green = self::tag_channel_linear( hexdec( substr( $hex, 3, 2 ) ) / 255 );
895 + $blue = self::tag_channel_linear( hexdec( substr( $hex, 5, 2 ) ) / 255 );
896 +
897 + return ( 0.2126 * $red ) + ( 0.7152 * $green ) + ( 0.0722 * $blue );
898 + }
899 +
900 + /**
901 + * Method tag_channel_linear()
902 + *
903 + * Convert an sRGB channel to linear light.
904 + *
905 + * @param float $channel Channel from 0 to 1.
906 + *
907 + * @return float Linear channel.
908 + */
909 + private static function tag_channel_linear( $channel ) {
910 + if ( $channel <= 0.04045 ) {
911 + return $channel / 12.92;
912 + }
913 +
914 + return pow( ( $channel + 0.055 ) / 1.055, 2.4 );
915 + }
916 +
917 + /**
918 + * Method get_site_tag_label()
919 + *
920 + * Build one tag chip for the linked and plain renderers.
921 + *
922 + * @param string $tag Tag name.
923 + * @param string $tag_id Tag id. An empty id renders a plain chip.
924 + * @param string $href Link prefix.
925 + * @param string $background Stored background color.
926 + *
927 + * @return string Chip HTML.
928 + */
929 + private static function get_site_tag_label( $tag, $tag_id, $href, $background ) {
930 + $foreground = static::get_tag_contrast_color( $background );
931 + $tag_style = '';
932 +
933 + if ( '' !== $foreground ) {
934 + $normalized = self::normalize_tag_color( $background );
935 + $tag_style = 'style="background-color:' . esc_attr( $normalized ) . ';color:' . esc_attr( $foreground ) . '"';
936 + }
937 +
938 + if ( ! empty( $tag_id ) ) {
939 + $tag_a_style = 'style="opacity:1;"';
940 + if ( '' !== $foreground ) {
941 + $tag_a_style = 'style="color:' . esc_attr( $foreground ) . '!important;opacity:1;"';
942 + }
943 +
944 + return '<span ' . $tag_style . ' tag_id="' . esc_attr( $tag_id ) . '" class="ui tag mini label"><a ' . $tag_a_style . ' href="' . esc_url( $href . $tag_id ) . '">' . esc_html( $tag ) . '</a></span>';
945 + }
946 +
947 + return '<span ' . $tag_style . ' class="ui tag mini label">' . esc_html( $tag ) . '</span>';
948 + }
949 +
950 + /**
804 951 * Filter client tags
805 952 *
806 953 * @param array $item Array containing tags.
807 954 *
@@ -1891,6 +2038,149 @@
1891 2038 return $const;
1892 2039 }
1893 2040 }
1894 2041 return $http_ver_int;
2042 + }
2043 +
2044 +
2045 + /**
2046 + * Method test_self_connect()
2047 + *
2048 + * Server self-connect test.
2049 + *
2050 + * @param string $url Dashboard self-connect URL.
2051 + *
2052 + * @return array|WP_Error
2053 + */
2054 + public static function test_self_connect( $url ) {
2055 +
2056 + $secret = static::get_self_connect_secret();
2057 + $timestamp = time();
2058 +
2059 + if ( empty( $secret ) ) {
2060 + return new \WP_Error(
2061 + 'mainwp_self_connect_secret',
2062 + __( 'Unable to generate self-connect secret.', 'mainwp' )
2063 + );
2064 + }
2065 +
2066 + $message = 'mainwp_self_connect|' . $timestamp;
2067 +
2068 + $signature = hash_hmac(
2069 + 'sha256',
2070 + $message,
2071 + $secret
2072 + );
2073 +
2074 + /**
2075 + * Filter: https_local_ssl_verify
2076 + *
2077 + * Filters whether the server-self check shoul verify SSL Cert.
2078 + *
2079 + * @since Unknown
2080 + */
2081 + $args = array(
2082 + 'blocking' => true,
2083 + 'sslverify' => apply_filters( 'https_local_ssl_verify', true ),
2084 + 'timeout' => 15,
2085 + 'headers' => array(
2086 + 'X-MainWP-Self-Connect-Timestamp' => (string) $timestamp,
2087 + 'X-MainWP-Self-Connect-Signature' => $signature,
2088 + ),
2089 + 'body' => array(
2090 + 'action' => 'mainwp_self_connect',
2091 + 'mainwp_run' => 'self_connect',
2092 + ),
2093 + );
2094 + return wp_remote_post( $url, $args );
2095 + }
2096 +
2097 +
2098 + /**
2099 + * Method handle_self_connect()
2100 + *
2101 + * Server self-connect response.
2102 + *
2103 + * @return void
2104 + */
2105 + public function handle_self_connect() {
2106 +
2107 + if ( ! $this->verify_self_connect_request() ) {
2108 + status_header( 403 );
2109 + exit;
2110 + }
2111 +
2112 + status_header( 200 );
2113 + header( 'Content-Type: text/plain; charset=utf-8' );
2114 +
2115 + echo 'MainWP Self Connect OK';
2116 + exit;
2117 + }
2118 +
2119 +
2120 + /**
2121 + * Method verify_self_connect_request()
2122 + *
2123 + * Server self-connect test.
2124 + *
2125 + * @return bool
2126 + */
2127 + private function verify_self_connect_request() {
2128 +
2129 + $signature = isset( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_SIGNATURE'] )
2130 + ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_SIGNATURE'] ) )
2131 + : '';
2132 +
2133 + $timestamp = isset( $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_TIMESTAMP'] )
2134 + ? (int) $_SERVER['HTTP_X_MAINWP_SELF_CONNECT_TIMESTAMP']
2135 + : 0;
2136 +
2137 + if ( 64 !== strlen( $signature ) || empty( $timestamp ) ) {
2138 + return false;
2139 + }
2140 +
2141 + // Prevent replay attacks.
2142 + if ( abs( time() - $timestamp ) > 300 ) {
2143 + return false;
2144 + }
2145 +
2146 + $secret = static::get_self_connect_secret();
2147 +
2148 + if ( empty( $secret ) ) {
2149 + return false;
2150 + }
2151 +
2152 + $message = 'mainwp_self_connect|' . $timestamp;
2153 +
2154 + $expected = hash_hmac(
2155 + 'sha256',
2156 + $message,
2157 + $secret
2158 + );
2159 +
2160 + return hash_equals( $expected, $signature );
2161 + }
2162 +
2163 +
2164 + /**
2165 + * Method get_self_connect_secret()
2166 + *
2167 + * @return string Self connect secret.
2168 + */
2169 + public static function get_self_connect_secret() {
2170 +
2171 + $option_name = 'mainwp_self_connect_secret';
2172 + $secret = get_option( $option_name, '' );
2173 +
2174 + if ( ! is_string( $secret ) || 64 !== strlen( $secret ) ) {
2175 + try {
2176 + $secret = bin2hex( random_bytes( 32 ) );
2177 + } catch ( \Exception $e ) {
2178 + return '';
2179 + }
2180 +
2181 + update_option( $option_name, $secret, false );
2182 + }
2183 +
2184 + return $secret;
1895 2185 }
1896 2186 }