class-seedlet-custom-colors.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php declare( strict_types = 1 ); ?>
  2. <?php
  3. /**
  4. * Seedlet Theme: Custom Colors Class
  5. *
  6. * This class is in charge of color customization via the Customizer.
  7. *
  8. * Each variable that needs to be updated is defined in the $seedlet_custom_color_variables array below.
  9. * A customizer setting is created for each color, and custom CSS-variables are enqueued in the front and back end.
  10. *
  11. * @package Seedlet
  12. * @since 1.0.0
  13. */
  14. class Seedlet_Custom_Colors {
  15. private $seedlet_custom_color_variables = array();
  16. function __construct() {
  17. /**
  18. * Define color variables
  19. */
  20. $seedlet_colors = array(
  21. array( '--global--color-background', '#FFFFFF', __( 'Background Color', 'seedlet' ) ),
  22. array( '--global--color-foreground', '#333333', __( 'Foreground Color', 'seedlet' ) ),
  23. array( '--global--color-primary', '#000000', __( 'Primary Color', 'seedlet' ) ),
  24. array( '--global--color-secondary', '#3C8067', __( 'Secondary Color', 'seedlet' ) ),
  25. array( '--global--color-tertiary', '#FAFBF6', __( 'Tertiary Color', 'seedlet' ) ),
  26. array( '--global--color-border', '#EFEFEF', __( 'Borders Color', 'seedlet' ) ),
  27. );
  28. $this->seedlet_custom_color_variables = apply_filters( 'seedlet_colors', $seedlet_colors );
  29. /**
  30. * Register Customizer actions
  31. */
  32. add_action( 'customize_register', array( $this, 'seedlet_customize_custom_colors_register' ) );
  33. /**
  34. * Enqueue color variables for customizer & frontend.
  35. */
  36. add_action( 'wp_enqueue_scripts', array( $this, 'seedlet_custom_color_variables' ) );
  37. /**
  38. * Enqueue color variables for editor.
  39. */
  40. add_action( 'enqueue_block_editor_assets', array( $this, 'seedlet_editor_custom_color_variables' ) );
  41. /**
  42. * Enqueue contrast checking.
  43. */
  44. add_action( 'customize_controls_enqueue_scripts', array( $this, 'on_customize_controls_enqueue_scripts' ) );
  45. }
  46. /**
  47. * Find the resulting colour by blending 2 colours
  48. * and setting an opacity level for the foreground colour.
  49. *
  50. * @author J de Silva
  51. * @link http://www.gidnetwork.com/b-135.html
  52. * @param string $foreground Hexadecimal colour value of the foreground colour.
  53. * @param integer $opacity Opacity percentage (of foreground colour). A number between 0 and 100.
  54. * @param string $background Optional. Hexadecimal colour value of the background colour. Default is: <code>FFFFFF</code> aka white.
  55. * @return string Hexadecimal colour value. <code>false</code> on errors.
  56. */
  57. function seedlet_color_blend_by_opacity( $foreground, $opacity, $background = null ) {
  58. static $colors_rgb = array(); // stores colour values already passed through the hexdec() functions below.
  59. if ( ! is_null( $foreground ) ) {
  60. $foreground = '000000'; // default primary.
  61. } else {
  62. $foreground = preg_replace( '/[^0-9a-f]/i', '', $foreground ); //str_replace( '#', '', $foreground );
  63. }
  64. if ( ! is_null( $background ) ) {
  65. $background = 'FFFFFF'; // default background.
  66. } else {
  67. $background = preg_replace( '/[^0-9a-f]/i', '', $background );
  68. }
  69. $pattern = '~^[a-f0-9]{6,6}$~i'; // accept only valid hexadecimal colour values.
  70. if ( ! @preg_match( $pattern, $foreground ) or ! @preg_match( $pattern, $background ) ) {
  71. echo $foreground;
  72. trigger_error( 'Invalid hexadecimal color value(s) found', E_USER_WARNING );
  73. return false;
  74. }
  75. $opacity = intval( $opacity ); // validate opacity data/number.
  76. if ( $opacity > 100 || $opacity < 0 ) {
  77. trigger_error( 'Opacity percentage error, valid numbers are between 0 - 100', E_USER_WARNING );
  78. return false;
  79. }
  80. if ( $opacity == 100 ) { // $transparency == 0
  81. return strtoupper( $foreground );
  82. }
  83. if ( $opacity == 0 ) { // $transparency == 100
  84. return strtoupper( $background );
  85. }
  86. // calculate $transparency value.
  87. $transparency = 100 - $opacity;
  88. if ( ! isset( $colors_rgb[ $foreground ] ) ) { // do this only ONCE per script, for each unique colour.
  89. $f = array(
  90. 'r' => hexdec( $foreground[0] . $foreground[1] ),
  91. 'g' => hexdec( $foreground[2] . $foreground[3] ),
  92. 'b' => hexdec( $foreground[4] . $foreground[5] ),
  93. );
  94. $colors_rgb[ $foreground ] = $f;
  95. } else { // if this function is used 100 times in a script, this block is run 99 times. Efficient.
  96. $f = $colors_rgb[ $foreground ];
  97. }
  98. if ( ! isset( $colors_rgb[ $background ] ) ) { // do this only ONCE per script, for each unique colour.
  99. $b = array(
  100. 'r' => hexdec( $background[0] . $background[1] ),
  101. 'g' => hexdec( $background[2] . $background[3] ),
  102. 'b' => hexdec( $background[4] . $background[5] ),
  103. );
  104. $colors_rgb[ $background ] = $b;
  105. } else { // if this FUNCTION is used 100 times in a SCRIPT, this block will run 99 times. Efficient.
  106. $b = $colors_rgb[ $background ];
  107. }
  108. $add = array(
  109. 'r' => ( $b['r'] - $f['r'] ) / 100,
  110. 'g' => ( $b['g'] - $f['g'] ) / 100,
  111. 'b' => ( $b['b'] - $f['b'] ) / 100,
  112. );
  113. $f['r'] += intval( $add['r'] * $transparency );
  114. $f['g'] += intval( $add['g'] * $transparency );
  115. $f['b'] += intval( $add['b'] * $transparency );
  116. return sprintf( '#%02X%02X%02X', $f['r'], $f['g'], $f['b'] );
  117. }
  118. /**
  119. * Add Theme Options for the Customizer.
  120. *
  121. * @param WP_Customize_Manager $wp_customize Theme Customizer object.
  122. */
  123. function seedlet_customize_custom_colors_register( $wp_customize ) {
  124. /**
  125. * Create color options panel.
  126. */
  127. $wp_customize->add_section(
  128. 'seedlet_options',
  129. array(
  130. 'capability' => 'edit_theme_options',
  131. 'title' => esc_html__( 'Colors', 'seedlet' ),
  132. )
  133. );
  134. /**
  135. * Create toggle between default and custom colors.
  136. */
  137. $wp_customize->add_setting(
  138. 'custom_colors_active',
  139. array(
  140. 'capability' => 'edit_theme_options',
  141. 'sanitize_callback' => array( $this, 'sanitize_select' ),
  142. 'transport' => 'refresh',
  143. 'default' => 'default',
  144. )
  145. );
  146. $wp_customize->add_control(
  147. 'custom_colors_active',
  148. array(
  149. 'type' => 'radio',
  150. 'section' => 'seedlet_options',
  151. 'label' => __( 'Colors', 'seedlet' ),
  152. 'choices' => array(
  153. 'default' => __( 'Theme Default', 'seedlet' ),
  154. 'custom' => __( 'Custom', 'seedlet' ),
  155. ),
  156. )
  157. );
  158. /**
  159. * Create customizer color controls.
  160. */
  161. foreach ( $this->seedlet_custom_color_variables as $variable ) {
  162. $wp_customize->add_setting(
  163. "seedlet_$variable[0]",
  164. array(
  165. 'default' => esc_html( $variable[1] ),
  166. 'sanitize_callback' => 'sanitize_hex_color',
  167. )
  168. );
  169. $wp_customize->add_control(
  170. new WP_Customize_Color_Control(
  171. $wp_customize,
  172. "seedlet_$variable[0]",
  173. array(
  174. 'section' => 'seedlet_options',
  175. 'label' => $variable[2],
  176. 'active_callback' => function() use ( $wp_customize ) {
  177. return ( 'custom' === $wp_customize->get_setting( 'custom_colors_active' )->value() );
  178. },
  179. )
  180. )
  181. );
  182. }
  183. }
  184. /**
  185. * Generate color variables.
  186. */
  187. function seedlet_generate_custom_color_variables( $context = null ) {
  188. if ( $context === 'editor' ) {
  189. $theme_css = ':root .editor-styles-wrapper {';
  190. } else {
  191. $theme_css = ':root {';
  192. }
  193. // Check to see if a custom background color has been set that is needed for our color calculation
  194. // If this check isn't present, the color calculation generates a warning that an invalid color has been supplied
  195. $theme_mod_bg_color = empty( get_theme_mod( 'seedlet_--global--color-background' ) ) ? '#FFFFFF' : get_theme_mod( 'seedlet_--global--color-background' );
  196. foreach ( $this->seedlet_custom_color_variables as $variable ) {
  197. if ( ! empty( get_theme_mod( "seedlet_$variable[0]" ) ) ) {
  198. $theme_mod_variable_name = $variable[0];
  199. $theme_mod_default_color = $variable[1];
  200. $theme_mod_custom_color = get_theme_mod( "seedlet_$variable[0]" );
  201. $opacity_integer = 70;
  202. $adjusted_color = $this->seedlet_color_blend_by_opacity( $theme_mod_custom_color, $opacity_integer, $theme_mod_bg_color );
  203. $theme_css .= $theme_mod_variable_name . ':' . $theme_mod_custom_color . ';';
  204. if ( $theme_mod_variable_name === '--global--color-primary' && $theme_mod_default_color !== $theme_mod_custom_color ) {
  205. $theme_css .= '--global--color-primary-hover: ' . $adjusted_color . ';';
  206. }
  207. if ( $theme_mod_variable_name === '--global--color-secondary' && $theme_mod_default_color !== $theme_mod_custom_color ) {
  208. $theme_css .= '--global--color-secondary-hover: ' . $adjusted_color . ';';
  209. }
  210. if ( $theme_mod_variable_name === '--global--color-foreground' && $theme_mod_default_color !== $theme_mod_custom_color ) {
  211. $theme_css .= '--global--color-foreground-low-contrast: ' . $adjusted_color . ';';
  212. }
  213. if ( $theme_mod_variable_name === '--global--color-background' && $theme_mod_default_color !== $theme_mod_custom_color ) {
  214. $theme_css .= '--global--color-background-high-contrast:' . $theme_mod_custom_color . ';';
  215. }
  216. }
  217. }
  218. $theme_css .= '}';
  219. // Text selection colors
  220. $selection_background = $this->seedlet_color_blend_by_opacity( get_theme_mod( 'seedlet_--global--color-primary' ), 5, $theme_mod_bg_color ) . ';';
  221. $theme_css .= '::selection { background-color: ' . $selection_background . '}';
  222. $theme_css .= '::-moz-selection { background-color: ' . $selection_background . '}';
  223. return $theme_css;
  224. }
  225. /**
  226. * Customizer & frontend custom color variables.
  227. */
  228. function seedlet_custom_color_variables() {
  229. wp_enqueue_style( 'seedlet-custom-color-overrides', get_template_directory_uri() . '/assets/css/custom-color-overrides.css', array(), wp_get_theme()->get( 'Version' ) );
  230. if ( 'default' !== get_theme_mod( 'custom_colors_active' ) ) {
  231. wp_add_inline_style( 'seedlet-custom-color-overrides', $this->seedlet_generate_custom_color_variables() );
  232. }
  233. }
  234. /**
  235. * Editor custom color variables.
  236. */
  237. function seedlet_editor_custom_color_variables() {
  238. wp_enqueue_style( 'seedlet-custom-color-overrides', get_template_directory_uri() . '/assets/css/custom-color-overrides.css', array(), wp_get_theme()->get( 'Version' ) );
  239. if ( 'default' !== get_theme_mod( 'custom_colors_active' ) ) {
  240. wp_add_inline_style( 'seedlet-custom-color-overrides', $this->seedlet_generate_custom_color_variables( 'editor' ) );
  241. }
  242. }
  243. /**
  244. * Customizer contrast validation warnings.
  245. *
  246. * @author Per Soderlind
  247. * @link https://github.com/soderlind/2016-customizer-demo
  248. */
  249. function on_customize_controls_enqueue_scripts() {
  250. $handle = 'seedlet-wcag-validate-customizer-color-contrast';
  251. $src = get_template_directory_uri() . '/assets/js/customizer-validate-wcag-color-contrast.js';
  252. $deps = array( 'customize-controls' );
  253. $exports = array(
  254. 'validate_color_contrast' => array(
  255. // key = current color control , values = array with color controls to check color contrast against
  256. 'seedlet_--global--color-primary' => array( 'seedlet_--global--color-background' ),
  257. 'seedlet_--global--color-secondary' => array( 'seedlet_--global--color-background' ),
  258. 'seedlet_--global--color-foreground' => array( 'seedlet_--global--color-background' ),
  259. 'seedlet_--global--color-background' => array( 'seedlet_--global--color-foreground' ),
  260. ),
  261. );
  262. wp_enqueue_script( $handle, $src, $deps );
  263. wp_script_add_data( $handle, 'data', sprintf( 'var seedletValidateWCAGColorContrastExports = %s;', wp_json_encode( $exports ) ) );
  264. // Custom color contrast validation text
  265. wp_localize_script( $handle, 'seedletValidateContrastText', array( esc_html__( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker foreground color.', 'seedlet' ) ) );
  266. }
  267. /**
  268. * Sanitize select.
  269. *
  270. * @param string $input The input from the setting.
  271. * @param object $setting The selected setting.
  272. *
  273. * @return string $input|$setting->default The input from the setting or the default setting.
  274. */
  275. public static function sanitize_select( $input, $setting ) {
  276. $input = sanitize_key( $input );
  277. $choices = $setting->manager->get_control( $setting->id )->choices;
  278. return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
  279. }
  280. }
  281. new Seedlet_Custom_Colors;