PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 3.1.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v3.1.0
5.12.0 5.13.0 5.13.1 5.11.0 5.10.0 5.9.0 5.8.0 5.8.1 5.7.0 5.6.2 5.6.3 5.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 All 47 releases
double-opt-in / core / UI.class.php

UI.class.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 3.1.0, at core/UI.class.php

696 lines 21.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace forge12\contactform7\CF7DoubleOptIn {
4
5 use Forge12\Shared\LoggerInterface;
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit;
9 }
10 /**
11 * dependencies
12 */
13 require_once( 'UIMenu.class.php' );
14
15 /**
16 * Class UI
17 *
18 * @package forge12\ui
19 */
20 class UI {
21 /**
22 * @var UI
23 */
24 private static $instance = null;
25
26 /**
27 * @var string
28 */
29 private $slug = '';
30 /**
31 * @var string
32 */
33 private $title = '';
34 /**
35 * @var string
36 */
37 private $capabilities;
38 /**
39 * Store all Pages
40 *
41 * @var array<UIPage>
42 */
43 private $pages;
44 /**
45 * Components
46 */
47 private $components = [];
48 /**
49 * @var TemplateHandler
50 */
51 private TemplateHandler $templateHandler;
52 private LoggerInterface $logger;
53
54 /**
55 * UI constructor.
56 *
57 * @param $slug
58 */
59 public function __construct( LoggerInterface $logger, TemplateHandler $templateHandler, string $slug, string $title, string $capabilities ) {
60 $this->logger = $logger;
61 $this->templateHandler = $templateHandler;
62 $this->slug = $slug;
63 $this->title = $title;
64 $this->capabilities = $capabilities;
65
66 $this->get_logger()->info( 'UI class instance created.', [
67 'plugin' => $this->slug,
68 'slug' => $this->slug,
69 'title' => $this->title,
70 'capabilities' => $this->capabilities,
71 ] );
72
73 $this->loadComponents();
74 $this->get_logger()->debug( 'UI components loaded.', ['plugin' => $this->slug] );
75
76 // Add Assets
77 add_action( 'admin_enqueue_scripts', array( $this, 'addAssets' ) );
78 $this->get_logger()->debug( 'Added "addAssets" action to "admin_enqueue_scripts".', ['plugin' => $this->slug] );
79
80 // Load Components after the Pages have been initialized
81 add_action( 'f12_cf7_doubleoptin_ui_after_load_pages', array( $this, 'registerComponents' ), 999999990, 2 );
82 $this->get_logger()->debug( 'Added "registerComponents" action to "f12_cf7_doubleoptin_ui_after_load_pages".', ['plugin' => $this->slug] );
83
84 // Sort Pages after intialize
85 add_action( 'f12_cf7_doubleoptin_ui_after_load_pages', array( $this, 'sortComponents' ), 999999999, 2 );
86 $this->get_logger()->debug( 'Added "sortComponents" action to "f12_cf7_doubleoptin_ui_after_load_pages".', ['plugin' => $this->slug] );
87
88 add_action( 'init', function () {
89 $this->get_logger()->debug( 'Triggering "f12_cf7_doubleoptin_ui_after_load_pages" action.', ['plugin' => $this->slug] );
90 do_action( 'f12_cf7_doubleoptin_ui_after_load_pages', $this, $this->slug );
91 } );
92
93 // Create the Submenus
94 add_action( 'admin_menu', array( $this, 'addSubmenuPagesToWordPress' ) );
95 $this->get_logger()->debug( 'Added "addSubmenuPagesToWordPress" action to "admin_menu".', ['plugin' => $this->slug] );
96
97 // Hotfix to hide the submenus that should be hidden but still callable in wordpress backend
98 add_action( 'admin_head', array( $this, 'hideSubmenuPagesToWordpress' ) );
99 $this->get_logger()->debug( 'Added "hideSubmenuPagesToWordpress" action to "admin_head".', ['plugin' => $this->slug] );
100
101 $this->get_logger()->notice( 'UI initialization complete.', ['plugin' => $this->slug] );
102 }
103
104 public function get_logger() {
105 return $this->logger;
106 }
107
108
109 /**
110 * Add a Page to the UI
111 *
112 * @param $UIPage
113 *
114 * @return void
115 */
116 public function addPage( $UIPage ) {
117 $this->get_logger()->info( 'Adding a new page to the UI.', [
118 'plugin' => $this->slug,
119 'page_class' => get_class( $UIPage ),
120 ] );
121
122 $this->pages[] = $UIPage;
123
124 // Add the renderContent action
125 add_action( 'forge12-plugin-content-' . $this->slug, array( $UIPage, 'renderContent' ), 10, 2 );
126 $this->get_logger()->debug( 'Added "renderContent" action for page: ' . get_class( $UIPage ), [
127 'plugin' => $this->slug,
128 'action_hook' => 'forge12-plugin-content-' . $this->slug,
129 ] );
130
131 // Add the renderSidebar action
132 add_action( 'forge12-plugin-sidebar-' . $this->slug, array( $UIPage, 'renderSidebar' ), 10, 2 );
133 $this->get_logger()->debug( 'Added "renderSidebar" action for page: ' . get_class( $UIPage ), [
134 'plugin' => $this->slug,
135 'action_hook' => 'forge12-plugin-sidebar-' . $this->slug,
136 ] );
137
138 $this->get_logger()->notice( 'Page successfully added to the UI.', [
139 'plugin' => $this->slug,
140 'total_pages' => count($this->pages),
141 ] );
142 }
143
144 /**
145 * @return array<UIPage>
146 */
147 private function getPages() {
148 $this->get_logger()->info( 'Retrieving the list of registered UI pages.', [
149 'plugin' => $this->slug,
150 ] );
151
152 // Check if the pages array is empty before returning.
153 if ( empty($this->pages) ) {
154 $this->get_logger()->warning( 'The list of UI pages is empty.', [
155 'plugin' => $this->slug,
156 ] );
157 } else {
158 $this->get_logger()->notice( 'Returning ' . count($this->pages) . ' registered UI pages.', [
159 'plugin' => $this->slug,
160 'page_count' => count($this->pages),
161 ] );
162 }
163
164 return $this->pages;
165 }
166
167 /**
168 * @param $slug
169 *
170 * @return UIPage|null
171 */
172 private function get( $slug ) {
173 $this->get_logger()->info( 'Attempting to retrieve a UI page by its slug.', [
174 'plugin' => $this->slug,
175 'requested_slug' => $slug,
176 ] );
177
178 foreach ( $this->pages as $UIPage ) {
179 if ( $UIPage->getSlug() == $slug ) {
180 $this->get_logger()->notice( 'UI page with slug "' . $slug . '" found.', [
181 'plugin' => $this->slug,
182 'found_slug' => $slug,
183 'page_class' => get_class($UIPage),
184 ] );
185 return $UIPage;
186 }
187 }
188
189 $this->get_logger()->warning( 'No UI page found for the requested slug "' . $slug . '".', [
190 'plugin' => $this->slug,
191 'requested_slug' => $slug,
192 ] );
193
194 return null;
195 }
196
197
198 public function addAssets() {
199 $this->get_logger()->info( 'Enqueuing admin assets.', [
200 'plugin' => $this->slug,
201 ] );
202
203 /**
204 * Stylesheets for the admin panel
205 */
206 wp_enqueue_style( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/admin-style.css', __FILE__ ), array(), '1.3' );
207 $this->get_logger()->debug( 'Enqueued stylesheet: f12-cf7-doubleoptin-admin', [
208 'plugin' => $this->slug,
209 ] );
210
211 /**
212 * Toggle Button
213 */
214 wp_enqueue_script( 'f12-cf7-doubleoptin-toggle', plugins_url( 'assets/toggle.js', __FILE__ ), array( 'jquery' ), '1.0' );
215 $this->get_logger()->debug( 'Enqueued script: f12-cf7-doubleoptin-toggle', [
216 'plugin' => $this->slug,
217 ] );
218
219 /**
220 * Copy to clipboard
221 */
222 wp_enqueue_script( 'f12-cf7-doubleoptin-copy', plugins_url( 'assets/copy-to-clipboard.js', __FILE__ ), array( 'jquery' ), '1.0' );
223 $this->get_logger()->debug( 'Enqueued script: f12-cf7-doubleoptin-copy', [
224 'plugin' => $this->slug,
225 ] );
226
227 /**
228 * External vendor scripts
229 */
230 wp_enqueue_script( 'vendor-highlight', plugins_url( 'assets/vendor/highlight/highlight.min.js', __FILE__ ), array( 'jquery' ), '1.0' );
231 $this->get_logger()->debug( 'Enqueued vendor script: vendor-highlight', [
232 'plugin' => $this->slug,
233 ] );
234
235 wp_enqueue_style( 'vendor-highlight', plugins_url( 'assets/vendor/highlight/default.min.css', __FILE__ ), array(), '1.0' );
236 $this->get_logger()->debug( 'Enqueued vendor stylesheet: vendor-highlight', [
237 'plugin' => $this->slug,
238 ] );
239
240 $this->get_logger()->notice( 'All admin assets have been enqueued successfully.', [
241 'plugin' => $this->slug,
242 ] );
243 }
244
245 public function sortComponents( $UI, $domain ) {
246 $this->get_logger()->info( 'Sorting UI pages based on their position.', [
247 'plugin' => $this->slug,
248 'domain' => $domain,
249 ] );
250
251 if ( ! empty( $this->pages ) ) {
252 $this->get_logger()->debug( 'Pages array is not empty. Proceeding with sort.', [
253 'plugin' => $this->slug,
254 'initial_page_order' => array_map(function($page) {
255 return ['slug' => $page->getSlug(), 'position' => $page->getPosition()];
256 }, $this->pages),
257 ] );
258
259 usort( $this->pages, function ( $a, $b ) {
260 if ( $a->getPosition() < $b->getPosition() ) {
261 return - 1;
262 } else if ( $a->getPosition() > $b->getPosition() ) {
263 return 1;
264 } else {
265 return 0;
266 }
267 } );
268
269 $this->get_logger()->notice( 'UI pages have been successfully sorted.', [
270 'plugin' => $this->slug,
271 'sorted_page_order' => array_map(function($page) {
272 return ['slug' => $page->getSlug(), 'position' => $page->getPosition()];
273 }, $this->pages),
274 ] );
275 } else {
276 $this->get_logger()->warning( 'Pages array is empty. Nothing to sort.', [
277 'plugin' => $this->slug,
278 ] );
279 }
280 }
281
282 /**
283 * @param UI $UI
284 * @param $domain
285 *
286 * @return void
287 */
288 public function registerComponents( $UI, $domain ) {
289 $this->get_logger()->info( 'Starting to register UI components.', [
290 'plugin' => $this->slug,
291 'domain' => $domain,
292 ] );
293
294 /**
295 * Load all registered components
296 */
297 foreach ( $this->components as $index => $component ) {
298 $this->get_logger()->debug( 'Processing component at index ' . $index, [
299 'plugin' => $this->slug,
300 'component' => $component,
301 ] );
302
303 /**
304 * Check if the path has been defined
305 */
306 if ( ! isset( $component['path'] ) ) {
307 $this->get_logger()->error( 'Component path not defined. Skipping component at index ' . $index . '.', [
308 'plugin' => $this->slug,
309 ] );
310 continue;
311 }
312
313 /**
314 * Check if the name has been defined
315 */
316 if ( ! isset( $component['name'] ) ) {
317 $this->get_logger()->error( 'Component name not defined. Skipping component at index ' . $index . '.', [
318 'plugin' => $this->slug,
319 ] );
320 continue;
321 }
322
323 /**
324 * Load the file
325 */
326 $component_path = $component['path'];
327 if ( ! file_exists( $component_path ) ) {
328 $this->get_logger()->error( 'Component file does not exist. Skipping.', [
329 'plugin' => $this->slug,
330 'path' => $component_path,
331 ] );
332 continue;
333 }
334
335 require_once( $component_path );
336 $this->get_logger()->debug( 'Component file loaded successfully.', [
337 'plugin' => $this->slug,
338 'path' => $component_path,
339 ] );
340
341 /**
342 * Load the instance of the object
343 */
344 $component_name = $component['name'];
345 if ( ! class_exists( $component_name ) ) {
346 $this->get_logger()->error( 'Component class "' . $component_name . '" not found after including the file. Skipping.', [
347 'plugin' => $this->slug,
348 'name' => $component_name,
349 ] );
350 continue;
351 }
352
353 $UIPage = new $component_name($this->get_logger(), $this->templateHandler, $domain );
354 $this->get_logger()->debug( 'Component class instantiated.', [
355 'plugin' => $this->slug,
356 'class' => $component_name,
357 ] );
358
359 /**
360 * Add the page to the user interface in the backend of wordpress.
361 */
362 $UI->addPage( $UIPage );
363 $this->get_logger()->notice( 'Component "' . $component_name . '" successfully registered and added as a page.', [
364 'plugin' => $this->slug,
365 ] );
366 }
367
368 $this->get_logger()->info( 'Finished registering all UI components.', [
369 'plugin' => $this->slug,
370 ] );
371 }
372
373 private function addComponent( $name, $path ) {
374 $this->get_logger()->info( 'Adding a new component to the registration list.', [
375 'plugin' => $this->slug,
376 'component_name' => $name,
377 'component_path' => $path,
378 ] );
379
380 $this->components[] = array(
381 'name' => $name,
382 'path' => $path
383 );
384
385 $this->get_logger()->notice( 'Component "' . $name . '" successfully added.', [
386 'plugin' => $this->slug,
387 'total_components' => count($this->components),
388 ] );
389 }
390
391 /**
392 * Retrieves the directories where the components are located.
393 *
394 * This method is responsible for retrieving the directories where the components
395 * for the current application are located. It uses the 'f12_cf7_doubleoption_ui_get_component_directories'
396 * filter to allow customization of the directories. By default, it returns an array
397 * containing the 'ui' directory located two levels above the current file's directory.
398 *
399 * @return array An array containing the directories where the components are located.
400 */
401 private function get_component_directories() {
402 $this->get_logger()->info( 'Retrieving component directories via filter.', [
403 'plugin' => $this->slug,
404 ] );
405
406 /**
407 * Component Directories
408 *
409 * This filter allows developers to add custom ui directories which will be used to display ui pages.
410 *
411 * @param array $directories
412 *
413 * @since 3.0.0
414 */
415 $default_directory = dirname( __FILE__, 2 ) . '/ui';
416 $this->get_logger()->debug( 'Default component directory: ' . $default_directory, [
417 'plugin' => $this->slug,
418 ] );
419
420 $directories = apply_filters( 'f12_cf7_doubleoption_ui_get_component_directories', [ $default_directory ] );
421
422 $this->get_logger()->notice( 'Component directories retrieved successfully.', [
423 'plugin' => $this->slug,
424 'directories' => $directories,
425 ] );
426
427 return $directories;
428 }
429
430 /**
431 * Load all components from the 'ui' directory.
432 *
433 * @return void
434 */
435 private function loadComponents() {
436 $this->get_logger()->info( 'Starting to load UI components from registered directories.', [
437 'plugin' => $this->slug,
438 ] );
439
440 $directories = $this->get_component_directories();
441
442 foreach ( $directories as $directory ) {
443 $this->get_logger()->debug( 'Processing directory: ' . $directory, [
444 'plugin' => $this->slug,
445 ] );
446
447 if ( is_dir( $directory ) ) {
448 $handle = opendir( $directory );
449
450 if ( ! $handle ) {
451 $this->get_logger()->error( 'Failed to open directory. Skipping.', [
452 'plugin' => $this->slug,
453 'directory' => $directory,
454 ] );
455 continue; // Continue to the next directory instead of returning
456 }
457
458 while ( false !== ( $entry = readdir( $handle ) ) ) {
459 if ( $entry != '.' && $entry != '..' ) {
460 $this->get_logger()->debug( 'Found directory entry: ' . $entry, [
461 'plugin' => $this->slug,
462 ] );
463
464 if ( preg_match( '!UI([a-zA-Z_0-9]+)\.class\.php!', $entry, $matches ) ) {
465 if ( isset( $matches[1] ) ) {
466 $component_name = '\\' . __NAMESPACE__ . '\UI' . $matches[1];
467 $component_path = $directory . '/' . $entry;
468
469 $this->get_logger()->info( 'Found a valid UI component file.', [
470 'plugin' => $this->slug,
471 'file' => $entry,
472 'component_name' => $component_name,
473 'component_path' => $component_path,
474 ] );
475
476 $this->addComponent( $component_name, $component_path );
477 } else {
478 $this->get_logger()->warning( 'Regex matched but group 1 was empty for file: ' . $entry, [
479 'plugin' => $this->slug,
480 ] );
481 }
482 } else {
483 $this->get_logger()->debug( 'File does not match component naming convention: ' . $entry, [
484 'plugin' => $this->slug,
485 ] );
486 }
487 }
488 }
489 closedir( $handle );
490 $this->get_logger()->notice( 'Finished processing directory: ' . $directory, [
491 'plugin' => $this->slug,
492 ] );
493 } else {
494 $this->get_logger()->warning( 'Directory does not exist or is not a directory. Skipping: ' . $directory, [
495 'plugin' => $this->slug,
496 ] );
497 }
498 }
499
500 $this->get_logger()->notice( 'All component directories have been processed.', [
501 'plugin' => $this->slug,
502 ] );
503 }
504
505 /**
506 * Add the WordPress Page for the Settings to the WordPress CMS
507 *
508 * @private WordPress Hook
509 */
510 public function addSubmenuPagesToWordPress() {
511 $this->get_logger()->info( 'Starting to add submenu pages to the WordPress admin menu.', [
512 'plugin' => $this->slug,
513 ] );
514
515 $icon_url = plugins_url( 'assets/icon-double-opt-in-20x20.png', dirname( __FILE__ ) );
516
517 // Add the main menu page
518 add_menu_page( $this->title, $this->title, $this->capabilities, $this->slug, '', $icon_url );
519 $this->get_logger()->notice( 'Main menu page added.', [
520 'plugin' => $this->slug,
521 'title' => $this->title,
522 'slug' => $this->slug,
523 'capability' => $this->capabilities,
524 ] );
525
526 // Add submenu pages for each registered UI page
527 foreach ( $this->getPages() as /** @var UIPage $Page */ $Page ) {
528 $this->get_logger()->debug( 'Processing page for submenu: ' . $Page->getSlug(), [
529 'plugin' => $this->slug,
530 'page_slug' => $Page->getSlug(),
531 'is_dashboard' => $Page->isDashboard(),
532 ] );
533
534 if ( $Page->isDashboard() ) {
535 $slug = $this->slug;
536 } else {
537 $slug = $this->slug . '_' . $Page->getSlug();
538 }
539
540 add_submenu_page(
541 $this->slug,
542 $Page->getTitle(),
543 $Page->getTitle(),
544 $this->capabilities,
545 $slug,
546 function () {
547 $this->render();
548 },
549 $Page->getPosition()
550 );
551
552 $this->get_logger()->notice( 'Submenu page added.', [
553 'plugin' => $this->slug,
554 'parent_slug' => $this->slug,
555 'submenu_title' => $Page->getTitle(),
556 'submenu_slug' => $slug,
557 'position' => $Page->getPosition(),
558 ] );
559 }
560
561 $this->get_logger()->info( 'Finished adding all submenu pages.', [
562 'plugin' => $this->slug,
563 ] );
564 }
565
566 /**
567 * This will ensure that the menu page will be removed before the rendering.
568 * This needs to be called from add_action('admin_head', ''); to be working.
569 *
570 * @return void
571 */
572 public function hideSubmenuPagesToWordpress() {
573 $this->get_logger()->info( 'Starting to hide submenu pages from the WordPress admin menu.', [
574 'plugin' => $this->slug,
575 ] );
576
577 foreach ( $this->getPages() as /** @var UIPage $Page */ $Page ) {
578 $this->get_logger()->debug( 'Processing page: ' . $Page->getSlug(), [
579 'plugin' => $this->slug,
580 'page_slug' => $Page->getSlug(),
581 'hide_in_menu' => $Page->hideInMenu(),
582 ] );
583
584 if ( ! $Page->hideInMenu() ) {
585 $this->get_logger()->debug( 'Page is not configured to be hidden. Skipping.', [
586 'plugin' => $this->slug,
587 ] );
588 continue;
589 }
590
591 if ( $Page->isDashboard() ) {
592 $slug = $this->slug;
593 } else {
594 $slug = $this->slug . '_' . $Page->getSlug();
595 }
596
597 remove_submenu_page( $this->slug, $slug );
598
599 $this->get_logger()->notice( 'Submenu page successfully hidden.', [
600 'plugin' => $this->slug,
601 'parent_slug' => $this->slug,
602 'submenu_slug' => $slug,
603 ] );
604 }
605
606 $this->get_logger()->info( 'Finished hiding all designated submenu pages.', [
607 'plugin' => $this->slug,
608 ] );
609 }
610
611 public function render() {
612 $this->get_logger()->info( 'Rendering the main UI page.', [
613 'plugin' => $this->slug,
614 ] );
615
616 // Sanitize and determine the current page slug
617 $page_query = sanitize_text_field( $_GET['page'] ?? '' );
618 $page_parts = explode( $this->slug, $page_query );
619 $page = '';
620 if (isset($page_parts[1])) {
621 $page = ltrim($page_parts[1], '_');
622 }
623
624 if ( empty( $page ) ) {
625 $page = $this->slug;
626 }
627
628 $this->get_logger()->debug( 'Determined current UI page slug: ' . $page, [
629 'plugin' => $this->slug,
630 'query_page' => $page_query,
631 ] );
632
633 $pages = $this->getPages();
634 $menuPages = array();
635
636 foreach ( $pages as $UIPage ) {
637 if ( ! $UIPage->hideInMenu() ) {
638 $menuPages[] = $UIPage;
639 }
640 }
641
642 $this->get_logger()->info( 'Filtered ' . count($menuPages) . ' pages for the menu.', [
643 'plugin' => $this->slug,
644 ] );
645
646 // Start rendering the HTML
647 ?>
648 <div class="forge12-plugin <?php esc_attr_e( $this->slug ); ?>">
649 <div class="forge12-plugin-header">
650 <div class="forge12-plugin-header-inner">
651 <img src="<?php echo esc_url( plugin_dir_url( __FILE__ ) ); ?>/assets/logo-forge12.png"
652 alt="Forge12 Interactvie GmbH" title="Forge12 Interactive GmbH"/>
653 </div>
654 </div>
655 <div class="forge12-plugin-menu">
656 <?php
657 $this->get_logger()->info( 'Triggering "f12_cf7_doubleoptin_admin_menu" action.', [
658 'plugin' => $this->slug,
659 ] );
660 do_action( 'f12_cf7_doubleoptin_admin_menu', $menuPages, $page, $this->slug );
661 ?>
662 </div>
663 <div class="forge12-plugin-content">
664 <div class="forge12-plugin-content-main">
665 <?php
666 $this->get_logger()->info( 'Triggering "forge12-plugin-content-' . $this->slug . '" action.', [
667 'plugin' => $this->slug,
668 'current_page_slug' => $page,
669 ] );
670 do_action( 'forge12-plugin-content-' . $this->slug, $this->slug, $page );
671 ?>
672 </div>
673 <div class="forge12-plugin-content-sidebar">
674 <?php
675 $this->get_logger()->info( 'Triggering "forge12-plugin-sidebar-' . $this->slug . '" action.', [
676 'plugin' => $this->slug,
677 'current_page_slug' => $page,
678 ] );
679 do_action( 'forge12-plugin-sidebar-' . $this->slug, $this->slug, $page );
680 ?>
681 </div>
682 </div>
683 <div class="forge12-plugin-footer">
684 <div class="forge12-plugin-footer-inner">
685 <img src="<?php echo esc_url( plugin_dir_url( __FILE__ ) ); ?>/assets/logo-forge12-dark.png"
686 alt="Forge12 Interactvie GmbH" title="Forge12 Interactive GmbH"/>
687 </div>
688 </div>
689 </div>
690 <?php
691 $this->get_logger()->notice( 'Main UI page rendering complete.', [
692 'plugin' => $this->slug,
693 ] );
694 }
695 }
696 }