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-cover.php

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

433 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Cover Style for Global Injector
4 *
5 * Defines the JSON schema, icons, and rendering logic for the Cover visual style.
6 * Cover style shows content as-is by default, and on hover displays a copy button
7 * with an overlay blur effect.
8 *
9 * @package CTC
10 * @since 5.0.0
11 */
12
13 namespace CTC\Global_Injector\Styles;
14
15 /**
16 * Cover Style Class
17 */
18 class Cover {
19
20 /**
21 * Get the default configuration for cover style.
22 *
23 * This is the single source of truth for the cover JSON schema.
24 * Used by both PHP renderer and localized to JS for admin preview.
25 *
26 * @return array Default cover configuration.
27 */
28 public static function get_default_config() {
29 return [
30 'text' => [
31 'button_text' => 'Copy',
32 'success_text' => 'Copied!',
33 ],
34 'icon' => [
35 'enabled' => true,
36 'position' => 'left',
37 'icon_key' => 'clipboard',
38 'custom_url' => null,
39 ],
40 'overlay' => [
41 'overlay_color' => '#0f172a',
42 'hover_overlay_color' => '#4f46e5',
43 'overlay_opacity' => 20,
44 'blur' => 2,
45 ],
46 'button' => [
47 'text_color' => '#0f172a',
48 'bg_color' => '#ffffff',
49 'hover_bg_color' => '#f1f5f9',
50 'border_radius' => 9999,
51 'font_size' => 10,
52 'padding_x' => 12,
53 'padding_y' => 6,
54 ],
55 ];
56 }
57
58 /**
59 * Get available icons for cover style.
60 *
61 * Each icon has a key and SVG markup.
62 * The key is stored in config, SVG is used for rendering.
63 *
64 * @return array Array of icons with key => SVG markup.
65 */
66 public static function get_icons() {
67 return [
68 'clipboard' => [
69 'label' => 'Clipboard',
70 '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>',
71 ],
72 'copy' => [
73 'label' => 'Copy',
74 '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>',
75 ],
76 'eye' => [
77 'label' => 'Eye',
78 '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 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>',
79 ],
80 'click' => [
81 'label' => 'Click',
82 '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 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"/></svg>',
83 ],
84 'lock' => [
85 'label' => 'Lock',
86 '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="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>',
87 ],
88 'check' => [
89 'label' => 'Checkmark',
90 '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>',
91 ],
92 ];
93 }
94
95 /**
96 * Get success icon (shown after copy).
97 *
98 * @return string SVG markup for success icon.
99 */
100 public static function get_success_icon() {
101 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>';
102 }
103
104 /**
105 * Generate a unique cover ID.
106 *
107 * @return string Unique cover ID.
108 */
109 public static function generate_cover_id() {
110 return 'ctc-cover-' . wp_generate_password( 8, false, false );
111 }
112
113 /**
114 * Merge user config with defaults.
115 *
116 * @param array $user_config User-provided configuration.
117 * @return array Merged configuration.
118 */
119 public static function merge_config( $user_config = [] ) {
120 $defaults = self::get_default_config();
121
122 // Deep merge.
123 $merged = $defaults;
124
125 if ( isset( $user_config['text'] ) && is_array( $user_config['text'] ) ) {
126 $merged['text'] = array_merge( $defaults['text'], $user_config['text'] );
127 }
128
129 if ( isset( $user_config['icon'] ) && is_array( $user_config['icon'] ) ) {
130 $merged['icon'] = array_merge( $defaults['icon'], $user_config['icon'] );
131 }
132
133 if ( isset( $user_config['overlay'] ) && is_array( $user_config['overlay'] ) ) {
134 $merged['overlay'] = array_merge( $defaults['overlay'], $user_config['overlay'] );
135 }
136
137 if ( isset( $user_config['button'] ) && is_array( $user_config['button'] ) ) {
138 $merged['button'] = array_merge( $defaults['button'], $user_config['button'] );
139 }
140
141 return $merged;
142 }
143
144 /**
145 * Get the icon SVG by key.
146 *
147 * @param string $icon_key Icon key.
148 * @return string SVG markup or empty string if not found.
149 */
150 public static function get_icon_svg( $icon_key ) {
151 $icons = self::get_icons();
152
153 if ( isset( $icons[ $icon_key ]['svg'] ) ) {
154 return $icons[ $icon_key ]['svg'];
155 }
156
157 // Fallback to clipboard icon.
158 return $icons['clipboard']['svg'];
159 }
160
161 /**
162 * Convert hex color to rgba.
163 *
164 * @param string $hex Hex color (e.g., '#0f172a').
165 * @param float $opacity Opacity (0-1).
166 * @return string RGBA color string.
167 */
168 public static function hex_to_rgba( $hex, $opacity = 1 ) {
169 // Remove # if present.
170 $hex = ltrim( $hex, '#' );
171
172 // Parse hex to RGB.
173 $r = hexdec( substr( $hex, 0, 2 ) );
174 $g = hexdec( substr( $hex, 2, 2 ) );
175 $b = hexdec( substr( $hex, 4, 2 ) );
176
177 return "rgba({$r}, {$g}, {$b}, {$opacity})";
178 }
179
180 /**
181 * Build inline CSS variables from config.
182 *
183 * @param array $config Cover configuration.
184 * @return string Inline style attribute value.
185 */
186 public static function build_inline_styles( $config ) {
187 $overlay = $config['overlay'];
188 $button = $config['button'];
189
190 // Calculate overlay opacity as decimal.
191 $overlay_opacity = isset( $overlay['overlay_opacity'] ) ? $overlay['overlay_opacity'] / 100 : 0.2;
192 $hover_opacity = min( $overlay_opacity + 0.1, 1 );
193
194 $css_vars = [
195 // Overlay styles.
196 '--ctc-cover-overlay' => self::hex_to_rgba( $overlay['overlay_color'], $overlay_opacity ),
197 '--ctc-cover-hover-overlay' => self::hex_to_rgba( $overlay['hover_overlay_color'], $hover_opacity ),
198 '--ctc-cover-blur' => ( isset( $overlay['blur'] ) ? $overlay['blur'] : 2 ) . 'px',
199 // Button styles.
200 '--ctc-cover-btn-text' => $button['text_color'],
201 '--ctc-cover-btn-bg' => $button['bg_color'],
202 '--ctc-cover-btn-hover-bg' => isset( $button['hover_bg_color'] ) ? $button['hover_bg_color'] : '#f1f5f9',
203 '--ctc-cover-btn-radius' => $button['border_radius'],
204 '--ctc-cover-btn-font-size' => $button['font_size'],
205 '--ctc-cover-btn-padding-x' => $button['padding_x'],
206 '--ctc-cover-btn-padding-y' => $button['padding_y'],
207 ];
208
209 // Properties that need 'px' unit.
210 $px_props = [
211 '--ctc-cover-btn-radius',
212 '--ctc-cover-btn-font-size',
213 '--ctc-cover-btn-padding-x',
214 '--ctc-cover-btn-padding-y',
215 ];
216
217 $inline = [];
218 foreach ( $css_vars as $var => $value ) {
219 // Add 'px' unit for numeric properties.
220 if ( in_array( $var, $px_props, true ) && is_numeric( $value ) ) {
221 $value = $value . 'px';
222 }
223 $inline[] = esc_attr( $var ) . ': ' . esc_attr( $value );
224 }
225
226 return implode( '; ', $inline );
227 }
228
229 /**
230 * Render the cover HTML.
231 *
232 * This renders the overlay element that wraps the target content.
233 * The actual copy functionality is handled by JS.
234 *
235 * @param array $config Cover configuration (merged with defaults).
236 * @param string $cover_id Optional. Unique cover ID. Auto-generated if not provided.
237 * @return string Cover HTML markup.
238 */
239 public static function render( $config = [], $cover_id = null ) {
240 // Merge with defaults.
241 $config = self::merge_config( $config );
242
243 // Generate cover ID if not provided.
244 if ( empty( $cover_id ) ) {
245 $cover_id = self::generate_cover_id();
246 }
247
248 // Get icon SVG.
249 $icon_svg = '';
250 if ( $config['icon']['enabled'] ) {
251 if ( ! empty( $config['icon']['custom_url'] ) ) {
252 // Pro feature: custom icon.
253 $icon_svg = '<img src="' . esc_url( $config['icon']['custom_url'] ) . '" alt="" class="ctc-cover-icon" />';
254 } else {
255 $icon_svg = '<span class="ctc-cover-icon">' . self::get_icon_svg( $config['icon']['icon_key'] ) . '</span>';
256 }
257 }
258
259 // Build inline styles.
260 $inline_styles = self::build_inline_styles( $config );
261
262 // Build button content based on icon position.
263 $button_content = '';
264 if ( 'left' === $config['icon']['position'] && $icon_svg ) {
265 $button_content .= $icon_svg;
266 }
267 $button_content .= '<span class="ctc-cover-text">' . esc_html( $config['text']['button_text'] ) . '</span>';
268 if ( 'right' === $config['icon']['position'] && $icon_svg ) {
269 $button_content .= $icon_svg;
270 }
271
272 // Build the cover overlay HTML.
273 $html = sprintf(
274 '<div id="%s" class="ctc-cover-overlay" style="%s" data-success-text="%s" data-original-text="%s">
275 <button type="button" class="ctc-cover-button" aria-label="%s">%s</button>
276 </div>',
277 esc_attr( $cover_id ),
278 esc_attr( $inline_styles ),
279 esc_attr( $config['text']['success_text'] ),
280 esc_attr( $config['text']['button_text'] ),
281 esc_attr( $config['text']['button_text'] ),
282 $button_content
283 );
284
285 return $html;
286 }
287
288 /**
289 * Get base CSS for cover style.
290 *
291 * This CSS uses CSS variables that are set inline on each cover element.
292 *
293 * @return string CSS styles.
294 */
295 public static function get_base_css() {
296 return '
297 /* Cover overlay - hidden by default, visible on hover */
298 .ctc-cover-overlay {
299 position: absolute;
300 inset: 0;
301 z-index: 20;
302 border-radius: inherit;
303 transition: all 0.3s ease;
304 cursor: pointer;
305
306 /* Hidden by default */
307 opacity: 0;
308 background: var(--ctc-cover-overlay, rgba(15, 23, 42, 0.2));
309 backdrop-filter: blur(0px);
310 -webkit-backdrop-filter: blur(0px);
311 }
312
313 /* Show overlay on hover */
314 .ctc-cover-overlay:hover {
315 opacity: 1;
316 background: var(--ctc-cover-hover-overlay, rgba(79, 70, 229, 0.3));
317 backdrop-filter: blur(var(--ctc-cover-blur, 2px));
318 -webkit-backdrop-filter: blur(var(--ctc-cover-blur, 2px));
319 }
320
321 /* Copy button - centered, appears with overlay on hover */
322 .ctc-cover-button {
323 position: absolute;
324 top: 50%;
325 left: 50%;
326 transform: translate(-50%, -50%) scale(0.9);
327 opacity: 0;
328 transition: all 0.2s ease;
329
330 /* Button styles from CSS variables */
331 color: var(--ctc-cover-btn-text, #0f172a);
332 background: var(--ctc-cover-btn-bg, rgba(255, 255, 255, 0.95));
333 font-size: var(--ctc-cover-btn-font-size, 10px);
334 font-weight: 700;
335 padding: var(--ctc-cover-btn-padding-y, 6px) var(--ctc-cover-btn-padding-x, 12px);
336 border-radius: var(--ctc-cover-btn-radius, 9999px);
337
338 /* Fixed styles */
339 display: inline-flex;
340 align-items: center;
341 gap: 6px;
342 border: 1px solid rgba(255, 255, 255, 0.5);
343 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
344 white-space: nowrap;
345 cursor: pointer;
346 }
347
348 /* Show button when overlay is hovered */
349 .ctc-cover-overlay:hover .ctc-cover-button {
350 opacity: 1;
351 transform: translate(-50%, -50%) scale(1);
352 }
353
354 .ctc-cover-button:hover {
355 background: var(--ctc-cover-btn-hover-bg, #f1f5f9);
356 transform: translate(-50%, -50%) scale(1.05);
357 }
358
359 .ctc-cover-button:active {
360 transform: translate(-50%, -50%) scale(0.95);
361 }
362
363 /* Icon styles */
364 .ctc-cover-icon {
365 display: inline-flex;
366 align-items: center;
367 justify-content: center;
368 }
369
370 .ctc-cover-icon svg {
371 width: 12px;
372 height: 12px;
373 }
374
375 .ctc-cover-icon img {
376 width: 12px;
377 height: 12px;
378 object-fit: contain;
379 }
380
381 .ctc-cover-text {
382 white-space: nowrap;
383 }
384
385 /* Success state */
386 .ctc-cover-overlay.ctc-copied .ctc-cover-button {
387 background: #059669;
388 color: #ffffff;
389 border-color: #059669;
390 }
391
392 /* Ensure parent has relative positioning */
393 .ctc-cover-wrapper {
394 position: relative;
395 }
396
397 /* Disable text selection on covered content */
398 .ctc-cover-wrapper.ctc-covered {
399 user-select: none;
400 }
401 ';
402 }
403
404 /**
405 * Get CSS for Global Injector inline output.
406 *
407 * Used by Inline_CSS::get_chunks(). Cover style uses the same selectors and
408 * CSS variables as get_base_css() for both shortcode/block and Global Injector.
409 *
410 * @since 5.0.2
411 * @return string Unminified CSS.
412 */
413 public static function get_global_injector_css() {
414 return self::get_base_css();
415 }
416
417 /**
418 * Get data to localize for JavaScript.
419 *
420 * This includes everything JS needs to render the cover preview.
421 *
422 * @return array Data for wp_localize_script.
423 */
424 public static function get_localize_data() {
425 return [
426 'defaultConfig' => self::get_default_config(),
427 'icons' => self::get_icons(),
428 'successIcon' => self::get_success_icon(),
429 'baseCSS' => self::get_base_css(),
430 ];
431 }
432 }
433