PluginProbe
Copy Anything to Clipboard for WordPress – Copy Button, Copy Text & Copy Code / 5.5.4
Copy Anything to Clipboard for WordPress – Copy Button, Copy Text & Copy Code v5.5.4
5.5.4 5.5.3 3.1.0 3.2.0 3.2.1 3.3.0 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.6.0 3.7.0 3.8.0 3.8.1 3.8.2 3.8.3 4.0.0 4.0.2 4.0.3 4.0.4 4.0.5 4.1.0 All 79 releases
copy-the-code / includes / global-injector / styles / class-button.php

class-button.php in Copy Anything to Clipboard for WordPress – Copy Button, Copy Text & Copy Code 5.5.4, at includes/global-injector/styles/class-button.php

413 lines 12.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Button Style for Global Injector
4 *
5 * Defines the JSON schema, icons, and rendering logic for the Button visual style.
6 * This class serves as the single source of truth for both PHP (frontend) and JS (admin preview).
7 *
8 * @package CTC
9 * @since 5.0.0
10 */
11
12 namespace CTC\Global_Injector\Styles;
13
14 /**
15 * Button Style Class
16 */
17 class Button {
18
19 /**
20 * Get the default configuration for button style.
21 *
22 * This is the single source of truth for the button JSON schema.
23 * Used by both PHP renderer and localized to JS for admin preview.
24 *
25 * @return array Default button configuration.
26 */
27 public static function get_default_config() {
28 return [
29 'text' => [
30 'button_text' => 'Copy Code',
31 'success_text' => 'Copied!',
32 ],
33 'icon' => [
34 'enabled' => true,
35 'position' => 'left', // 'left' or 'right'
36 'icon_key' => 'clipboard',
37 'custom_url' => null, // Pro feature: custom icon URL
38 ],
39 'style' => [
40 'text_color' => '#ffffff',
41 'background_color' => '#4f46e5',
42 'hover_background_color' => '#4338ca',
43 'font_size' => '12px',
44 'font_weight' => '700',
45 'padding_x' => '12px',
46 'padding_y' => '6px',
47 'border_radius' => '6px',
48 ],
49 ];
50 }
51
52 /**
53 * Get available icons for button style.
54 *
55 * Each icon has a key and SVG markup.
56 * The key is stored in config, SVG is used for rendering.
57 *
58 * @return array Array of icons with key => SVG markup.
59 */
60 public static function get_icons() {
61 return [
62 'clipboard' => [
63 'label' => 'Clipboard',
64 'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>',
65 ],
66 'copy' => [
67 'label' => 'Copy',
68 'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"/></svg>',
69 ],
70 'paperclip' => [
71 'label' => 'Attachment',
72 'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/></svg>',
73 ],
74 'link' => [
75 'label' => 'Link',
76 'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>',
77 ],
78 'document' => [
79 'label' => 'Document',
80 'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>',
81 ],
82 'check' => [
83 'label' => 'Checkmark',
84 'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>',
85 ],
86 ];
87 }
88
89 /**
90 * Get success icon (shown after copy).
91 *
92 * @return string SVG markup for success icon.
93 */
94 public static function get_success_icon() {
95 return '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>';
96 }
97
98 /**
99 * Generate a unique button ID.
100 *
101 * @return string Unique button ID.
102 */
103 public static function generate_button_id() {
104 return 'ctc-copy-btn-' . wp_generate_password( 8, false, false );
105 }
106
107 /**
108 * Merge user config with defaults.
109 *
110 * @param array $user_config User-provided configuration.
111 * @return array Merged configuration.
112 */
113 public static function merge_config( $user_config = [] ) {
114 $defaults = self::get_default_config();
115
116 // Deep merge
117 $merged = $defaults;
118
119 if ( isset( $user_config['text'] ) && is_array( $user_config['text'] ) ) {
120 $merged['text'] = array_merge( $defaults['text'], $user_config['text'] );
121 }
122
123 if ( isset( $user_config['icon'] ) && is_array( $user_config['icon'] ) ) {
124 $merged['icon'] = array_merge( $defaults['icon'], $user_config['icon'] );
125 }
126
127 if ( isset( $user_config['style'] ) && is_array( $user_config['style'] ) ) {
128 $merged['style'] = array_merge( $defaults['style'], $user_config['style'] );
129 }
130
131 return $merged;
132 }
133
134 /**
135 * Get the icon SVG by key.
136 *
137 * @param string $icon_key Icon key.
138 * @return string SVG markup or empty string if not found.
139 */
140 public static function get_icon_svg( $icon_key ) {
141 $icons = self::get_icons();
142
143 if ( isset( $icons[ $icon_key ]['svg'] ) ) {
144 return $icons[ $icon_key ]['svg'];
145 }
146
147 // Fallback to clipboard icon
148 return $icons['clipboard']['svg'];
149 }
150
151 /**
152 * Darken a hex color by a percentage.
153 *
154 * @param string $hex Hex color (e.g., '#4f46e5').
155 * @param int $percent Percentage to darken (0-100).
156 * @return string Darkened hex color.
157 */
158 public static function darken_color( $hex, $percent = 10 ) {
159 // Remove # if present.
160 $hex = ltrim( $hex, '#' );
161
162 // Parse hex to RGB.
163 $r = hexdec( substr( $hex, 0, 2 ) );
164 $g = hexdec( substr( $hex, 2, 2 ) );
165 $b = hexdec( substr( $hex, 4, 2 ) );
166
167 // Darken each channel.
168 $factor = 1 - ( $percent / 100 );
169 $r = (int) round( $r * $factor );
170 $g = (int) round( $g * $factor );
171 $b = (int) round( $b * $factor );
172
173 // Ensure values are in valid range.
174 $r = max( 0, min( 255, $r ) );
175 $g = max( 0, min( 255, $g ) );
176 $b = max( 0, min( 255, $b ) );
177
178 // Convert back to hex.
179 return '#' . sprintf( '%02x%02x%02x', $r, $g, $b );
180 }
181
182 /**
183 * Build inline CSS variables from style config.
184 * Auto-calculates hover background if not provided (darkens by 12%).
185 *
186 * @param array $style Style configuration.
187 * @return string Inline style attribute value.
188 */
189 public static function build_inline_styles( $style ) {
190 // Auto-calculate hover background if not provided.
191 $bg_color = isset( $style['background_color'] ) ? $style['background_color'] : '#4f46e5';
192 $hover_bg = isset( $style['hover_background_color'] ) && ! empty( $style['hover_background_color'] )
193 ? $style['hover_background_color']
194 : self::darken_color( $bg_color, 12 );
195
196 $css_vars = [
197 '--ctc-text-color' => $style['text_color'],
198 '--ctc-bg' => $bg_color,
199 '--ctc-hover-bg' => $hover_bg,
200 '--ctc-font-size' => $style['font_size'],
201 '--ctc-font-weight' => $style['font_weight'],
202 '--ctc-padding-x' => $style['padding_x'],
203 '--ctc-padding-y' => $style['padding_y'],
204 '--ctc-border-radius' => $style['border_radius'],
205 ];
206
207 // Map CSS var names to property keys for unit detection.
208 $var_to_prop = [
209 '--ctc-font-size' => 'font_size',
210 '--ctc-padding-x' => 'padding_x',
211 '--ctc-padding-y' => 'padding_y',
212 '--ctc-border-radius' => 'border_radius',
213 ];
214
215 $inline = [];
216 foreach ( $css_vars as $var => $value ) {
217 // Add 'px' unit for numeric properties.
218 if ( isset( $var_to_prop[ $var ] ) && is_numeric( $value ) ) {
219 $value = $value . 'px';
220 }
221 $inline[] = esc_attr( $var ) . ': ' . esc_attr( $value );
222 }
223
224 return implode( '; ', $inline );
225 }
226
227 /**
228 * Render the button HTML.
229 *
230 * This is the main render function used on the frontend.
231 * The same logic should be replicated in JS for admin preview.
232 *
233 * @param array $config Button configuration (merged with defaults).
234 * @param string $button_id Optional. Unique button ID. Auto-generated if not provided.
235 * @return string Button HTML markup.
236 */
237 public static function render( $config = [], $button_id = null ) {
238 // Merge with defaults
239 $config = self::merge_config( $config );
240
241 // Generate button ID if not provided
242 if ( empty( $button_id ) ) {
243 $button_id = self::generate_button_id();
244 }
245
246 // Get icon SVG
247 $icon_svg = '';
248 if ( $config['icon']['enabled'] ) {
249 if ( ! empty( $config['icon']['custom_url'] ) ) {
250 // Pro feature: custom icon
251 $icon_svg = '<img src="' . esc_url( $config['icon']['custom_url'] ) . '" alt="" class="ctc-btn-icon" />';
252 } else {
253 $icon_svg = '<span class="ctc-btn-icon">' . self::get_icon_svg( $config['icon']['icon_key'] ) . '</span>';
254 }
255 }
256
257 // Build inline styles
258 $inline_styles = self::build_inline_styles( $config['style'] );
259
260 // Build button content based on icon position
261 $button_content = '';
262 if ( $config['icon']['position'] === 'left' && $icon_svg ) {
263 $button_content .= $icon_svg;
264 }
265 $button_content .= '<span class="ctc-btn-text">' . esc_html( $config['text']['button_text'] ) . '</span>';
266 if ( $config['icon']['position'] === 'right' && $icon_svg ) {
267 $button_content .= $icon_svg;
268 }
269
270 // Build the button HTML
271 $html = sprintf(
272 '<button id="%s" class="ctc-copy-button" style="%s" data-success-text="%s" data-original-text="%s" aria-label="%s">%s</button>',
273 esc_attr( $button_id ),
274 esc_attr( $inline_styles ),
275 esc_attr( $config['text']['success_text'] ),
276 esc_attr( $config['text']['button_text'] ),
277 esc_attr( $config['text']['button_text'] ),
278 $button_content
279 );
280
281 return $html;
282 }
283
284 /**
285 * Get base CSS for button style.
286 *
287 * This CSS uses CSS variables that are set inline on each button.
288 * These are the "hidden" styles that users don't customize via UI.
289 *
290 * @return string CSS styles.
291 */
292 public static function get_base_css() {
293 return '
294 .ctc-copy-button {
295 /* User-customizable via CSS variables */
296 color: var(--ctc-text-color, #ffffff);
297 background-color: var(--ctc-bg, #4f46e5);
298 font-size: var(--ctc-font-size, 12px);
299 font-weight: var(--ctc-font-weight, 700);
300 padding: var(--ctc-padding-y, 6px) var(--ctc-padding-x, 12px);
301 border-radius: var(--ctc-border-radius, 6px);
302
303 /* Hidden CSS - always applied, not customizable via UI */
304 display: inline-flex;
305 align-items: center;
306 gap: 4px;
307 border: none;
308 cursor: pointer;
309 transition: background-color 0.2s ease;
310 position: absolute;
311 top: 8px;
312 right: 8px;
313 z-index: 10;
314 font-family: inherit;
315 line-height: 1;
316 }
317
318 .ctc-copy-button:hover {
319 background-color: var(--ctc-hover-bg, #4338ca);
320 }
321
322 .ctc-copy-button:focus {
323 outline: 2px solid var(--ctc-bg, #4f46e5);
324 outline-offset: 2px;
325 }
326
327 .ctc-copy-button .ctc-btn-icon {
328 display: inline-flex;
329 align-items: center;
330 justify-content: center;
331 }
332
333 .ctc-copy-button .ctc-btn-icon svg {
334 width: 14px;
335 height: 14px;
336 }
337
338 .ctc-copy-button .ctc-btn-icon img {
339 width: 14px;
340 height: 14px;
341 object-fit: contain;
342 }
343
344 .ctc-copy-button .ctc-btn-text {
345 white-space: nowrap;
346 }
347
348 /* Success state */
349 .ctc-copy-button.ctc-copied {
350 background-color: #059669;
351 }
352
353 .ctc-copy-button.ctc-copied:hover {
354 background-color: #047857;
355 }
356 ';
357 }
358
359 /**
360 * Get stylesheet CSS for Global Injector (frontend).
361 *
362 * Used when outputting inline CSS for Global Injector rules. Uses selectors
363 * .ctc-copy-btn--button and the same CSS variables as build_inline_styles().
364 * This is the Global Injector counterpart to get_base_css() (which targets .ctc-copy-button for shortcode/block).
365 *
366 * @return string Unminified CSS.
367 */
368 public static function get_global_injector_css() {
369 return '
370 .ctc-copy-btn--button {
371 padding: var(--ctc-padding-y, 8px) var(--ctc-padding-x, 16px);
372 background: var(--ctc-bg, #4f46e5);
373 color: var(--ctc-text-color, #fff);
374 border-radius: var(--ctc-border-radius, 6px);
375 font-size: var(--ctc-font-size, 13px);
376 }
377 .ctc-copy-btn--button:hover {
378 background: var(--ctc-hover-bg, #4338ca);
379 filter: brightness(1.05);
380 }
381 .ctc-copy-btn__icon {
382 display: inline-flex;
383 align-items: center;
384 justify-content: center;
385 flex-shrink: 0;
386 }
387 .ctc-copy-btn__icon svg {
388 width: 14px;
389 height: 14px;
390 }
391 .ctc-copy-btn__text {
392 white-space: nowrap;
393 }
394 ';
395 }
396
397 /**
398 * Get data to localize for JavaScript.
399 *
400 * This includes everything JS needs to render the button preview.
401 *
402 * @return array Data for wp_localize_script.
403 */
404 public static function get_localize_data() {
405 return [
406 'defaultConfig' => self::get_default_config(),
407 'icons' => self::get_icons(),
408 'successIcon' => self::get_success_icon(),
409 'baseCSS' => self::get_base_css(),
410 ];
411 }
412 }
413