From fc04b4c5f23d0333efa21f319c74b8410290488b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= <lucas@codeccoop.org>
Date: Wed, 31 Jan 2024 08:38:20 +0100
Subject: [PATCH] feat: plugin scaffolding

---
 includes/class-base-settings.php              | 56 +++++++++-------
 .../{class-api.php => class-http-client.php}  | 14 ++--
 includes/class-menu.php                       | 14 +---
 includes/class-plugin.php                     | 67 +++++++++++++++++++
 includes/class-settings.php                   |  8 +--
 includes/class-singleton.php                  | 32 +++++++++
 includes/fieldset-control-js.php              | 49 ++++++++++++++
 wpct-http-backend.php                         | 44 +++++-------
 8 files changed, 208 insertions(+), 76 deletions(-)
 rename includes/{class-api.php => class-http-client.php} (85%)
 create mode 100644 includes/class-plugin.php
 create mode 100644 includes/class-singleton.php
 create mode 100644 includes/fieldset-control-js.php

diff --git a/includes/class-base-settings.php b/includes/class-base-settings.php
index 3a3dbf1..a28f67c 100644
--- a/includes/class-base-settings.php
+++ b/includes/class-base-settings.php
@@ -2,26 +2,26 @@
 
 namespace WPCT_HB;
 
-use Exception;
-
 class Undefined
 {
 };
 
-class BaseSettings
+abstract class BaseSettings extends Singleton
 {
 
-    public $group_name;
+    protected $group_name;
     private $_defaults = [];
 
-    public function get_name()
+    abstract public function register();
+
+    public function __construct($textdomain)
     {
-        return $this->group_name;
+        $this->group_name = $textdomain;
     }
 
-    public function register()
+    public function get_name()
     {
-        throw new Exception('You have to overwrite this method');
+        return $this->group_name;
     }
 
     public function register_setting($name, $default = [])
@@ -39,9 +39,9 @@ class BaseSettings
 
         add_settings_section(
             $name . '_section',
-            __($name . '--title', 'wpct-http-backend'),
+            __($name . '--title', $this->group_name),
             function () use ($name) {
-                $title = __($name . '--description', 'wpct-http-backend');
+                $title = __($name . '--description', $this->group_name);
                 echo "<p>{$title}</p>";
             },
             $this->group_name,
@@ -55,7 +55,7 @@ class BaseSettings
         $field_id = $setting_name . '__' . $field_name;
         add_settings_field(
             $field_name,
-            __($field_id . '--label', 'wpct-http-backend'),
+            __($field_id . '--label', $this->group_name),
             function () use ($setting_name, $field_name) {
                 echo $this->field_render($setting_name, $field_name);
             },
@@ -90,12 +90,28 @@ class BaseSettings
 
     public function input_render($setting, $field, $value)
     {
-        return "<input type='text' name='{$setting}[{$field}]' value='{$value}' />";
+        $default_value = $this->get_default($setting);
+        $keys = explode('][', $field);
+        for ($i = 0; $i < count($keys); $i++) {
+            $default_value = isset($default_value[$keys[$i]]) ? $default_value[$keys[$i]] : $default_value[0];
+        }
+        $is_bool = is_bool($default_value);
+        if ($is_bool) {
+            $is_bool = true;
+            $value = 'on' === $value;
+        }
+
+        if ($is_bool) {
+            return "<input type='checkbox' name='{$setting}[$field]' " . ($value ? 'checked' : '') . " />";
+        } else {
+            return "<input type='text' name='{$setting}[{$field}]' value='{$value}' />";
+        }
     }
 
     public function fieldset_render($setting, $field, $data)
     {
-        $fieldset = "<table id='{$setting}[{$field}]'>";
+        $table_id = $setting . '__' . str_replace('][', '_', $field);
+        $fieldset = "<table id='{$table_id}'>";
         $is_list = is_list($data);
         foreach (array_keys($data) as $key) {
             $fieldset .= '<tr>';
@@ -109,31 +125,23 @@ class BaseSettings
         return $fieldset;
     }
 
-    public function default_values()
-    {
-        throw new Exception('You have to overwrite this method');
-    }
-
     public function control_render($setting, $field)
     {
-        $values = $this->get_default($setting);
-        error_log(print_r($values, true));
+        $defaults = $this->get_default($setting);
         ob_start();
 ?>
         <div class="<?= $setting; ?>__<?= $field ?>--controls">
             <button class="button button-primary" data-action="add">Add</button>
             <button class="button button-secondary" data-action="remove">Remove</button>
         </div>
-        <script>
-            <?php include 'fieldsetControl.js' ?>
-        </script>
+        <?php include 'fieldset-control-js.php' ?>
 <?php
         return ob_get_clean();
     }
 
     public function control_style($setting, $field)
     {
-        return "<style>.{$setting}_{$field} td td, .{$setting}_{$field} td th{padding:0}.{$setting}_{$field} table table{margin-bottom:1rem}</style>";
+        return "<style>#{$setting}__{$field} td td,#{$setting}__{$field} td th{padding:0}#{$setting}__{$field} table table{margin-bottom:1rem}</style>";
     }
 
     public function option_getter($setting, $option)
diff --git a/includes/class-api.php b/includes/class-http-client.php
similarity index 85%
rename from includes/class-api.php
rename to includes/class-http-client.php
index b95d364..82dde6f 100644
--- a/includes/class-api.php
+++ b/includes/class-http-client.php
@@ -68,7 +68,7 @@ class Http_Client
         if (isset($url_data['scheme'])) {
             return $url;
         } else {
-            $base_url = Http_Client::option_getter('base_url');
+            $base_url = Http_Client::option_getter('wpct-http-backend_general', 'base_url');
             return preg_replace('/\/$/', '', $base_url . '/' . preg_replace('/^\//', '', $url));
         }
     }
@@ -77,17 +77,17 @@ class Http_Client
     {
         $headers['Connection'] = 'keep-alive';
         $headers['Accept'] = 'application/json';
-        $headers['API-KEY'] = Http_Client::option_getter('api_key');
+        $headers['API-KEY'] = Http_Client::option_getter('wpct-http-backend_general', 'api_key');
         $headers['Accept-Language'] = Http_Client::get_locale();
 
-        return apply_filter('wpct_hb_headers', $headers, $method, $url);
+        return apply_filters('wpct_hb_headers', $headers, $method, $url);
     }
 
-    private static function option_getter($option)
+    private static function option_getter($setting, $option)
     {
-        $setting = get_option('wpct_hb');
+        $setting = get_option($setting);
         if (!$setting) {
-            throw new Exception('Wpct Http Backend: You should configure base url on plugin settings');
+            throw new \Exception('Wpct Http Backend: You should configure base url on plugin settings');
         }
 
         return isset($setting[$option]) ? $setting[$option] : null;
@@ -95,7 +95,7 @@ class Http_Client
 
     private static function get_locale()
     {
-        $locale = apply_filter('wpct_st_current_language', null, 'locale');
+        $locale = apply_filters('wpct_st_current_language', null, 'locale');
         if ($locale) return $locale;
 
         return get_locale();
diff --git a/includes/class-menu.php b/includes/class-menu.php
index f15e961..58cb73e 100644
--- a/includes/class-menu.php
+++ b/includes/class-menu.php
@@ -2,25 +2,22 @@
 
 namespace WPCT_HB;
 
-class Menu
+class Menu extends Singleton
 {
     private $name;
     private $settings;
 
-    public function __construct($name, $settings)
+    protected function __construct($name, $settings)
     {
         $this->name = $name;
         $this->settings = $settings;
-    }
 
-    public function on_load()
-    {
         add_action('admin_menu', function () {
             $this->add_menu();
         });
 
         add_action('admin_init', function () {
-            $this->register_settings();
+            $this->settings->register();
         });
     }
 
@@ -37,11 +34,6 @@ class Menu
         );
     }
 
-    private function register_settings()
-    {
-        $this->settings->register();
-    }
-
     private function render_page()
     {
         ob_start();
diff --git a/includes/class-plugin.php b/includes/class-plugin.php
new file mode 100644
index 0000000..68952fa
--- /dev/null
+++ b/includes/class-plugin.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace WPCT_HB;
+
+abstract class Plugin extends Singleton
+{
+    protected $name;
+    protected $textdomain;
+    private $menu;
+    protected $dependencies = [];
+
+    abstract public function init();
+
+    abstract public static function activate();
+
+    abstract public static function deactivate();
+
+    public function __construct()
+    {
+        if (empty($this->name) || empty($this->textdomain)) {
+            throw new \Exception('Bad plugin initialization');
+        }
+
+        $this->load_textdomain();
+        $this->check_dependencies();
+
+        $settings = Settings::get_instance($this->textdomain);
+        $this->menu = Menu::get_instance($this->name, $settings);
+
+        add_action('init', [$this, 'init']);
+    }
+
+    public function get_menu()
+    {
+        return $this->menu;
+    }
+
+    public function get_name()
+    {
+        return $this->name;
+    }
+
+    public function get_textdomain()
+    {
+        return $this->textdomain;
+    }
+
+    private function check_dependencies()
+    {
+        add_filter('wpct_dependencies_check', function ($dependencies) {
+            foreach ($this->dependencies as $label => $url) {
+                $dependencies[$label] = $url;
+            }
+
+            return $dependencies;
+        });
+    }
+
+    private function load_textdomain()
+    {
+        load_plugin_textdomain(
+            $this->textdomain,
+            false,
+            dirname(plugin_basename(__FILE__)) . '/languages',
+        );
+    }
+}
diff --git a/includes/class-settings.php b/includes/class-settings.php
index 5e9503d..69ecce1 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -6,8 +6,6 @@ require_once 'class-base-settings.php';
 
 class Settings extends BaseSettings
 {
-    public $group_name = 'wpct-http-backend';
-
     public function register()
     {
         $url = parse_url(get_site_url());
@@ -15,9 +13,9 @@ class Settings extends BaseSettings
         $this->register_setting(
             $setting_name,
             [
-                'base_url' => 'https://backend.' . $url['host'],
-                'api_key' => '123456789',
-            ]
+                'base_url' => 'http://example.' . $url['host'],
+                'api_key' => 'backend-api-key'
+            ],
         );
 
         $this->register_field('base_url', $setting_name);
diff --git a/includes/class-singleton.php b/includes/class-singleton.php
new file mode 100644
index 0000000..959fdf4
--- /dev/null
+++ b/includes/class-singleton.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace WPCT_HB;
+
+abstract class Singleton
+{
+    private static $_instances = [];
+
+    protected function __construct()
+    {
+    }
+
+    protected function __clone()
+    {
+    }
+
+    public function __wakeup()
+    {
+        throw new Exception('Cannot unserialize a singleton.');
+    }
+
+    public static function get_instance()
+    {
+        $args = func_get_args();
+        $cls = static::class;
+        if (!isset(self::$_instances[$cls])) {
+            self::$_instances[$cls] = new static(...$args);
+        }
+
+        return self::$_instances[$cls];
+    }
+}
diff --git a/includes/fieldset-control-js.php b/includes/fieldset-control-js.php
new file mode 100644
index 0000000..04a4a70
--- /dev/null
+++ b/includes/fieldset-control-js.php
@@ -0,0 +1,49 @@
+<?php
+// Script to be buffered from settings class
+$default_value = $defaults[$field][0];
+$is_array = is_array($default_value);
+$table_id = $setting . '__' . str_replace('][', '_', $field);
+?>
+
+<script>
+    (function() {
+        function renderRowContent(index) {
+            <?php if ($is_array) : ?>
+                return `<table id="<?= $table_id ?>_${index}">
+          <?php foreach (array_keys($default_value) as $key) : ?>
+            <tr>
+                <th><?= $field ?></th>
+                <td><?= $this->input_render($setting, $field . '][${index}][' . $key, $default_value[$key]); ?></td>
+            </tr>
+          <?php endforeach; ?>
+        </table>`;
+            <?php else : ?>
+                return `<?= $this->input_render($setting, $field . '][${index}', $default_value); ?>`;
+            <?php endif; ?>
+        }
+
+        function addItem(ev) {
+            ev.preventDefault();
+            const table = document.getElementById("<?= $table_id ?>")
+                .children[0];
+            const tr = document.createElement("tr");
+            tr.innerHTML =
+                "<td>" + renderRowContent(table.children.length) + "</td>";
+            table.appendChild(tr);
+        }
+
+        function removeItem(ev) {
+            ev.preventDefault();
+            const table = document.getElementById("<?= $table_id ?>")
+                .children[0];
+            const rows = table.children;
+            table.removeChild(rows[rows.length - 1]);
+        }
+
+        const buttons = document.currentScript.previousElementSibling.querySelectorAll("button");
+        buttons.forEach((btn) => {
+            const callback = btn.dataset.action === "add" ? addItem : removeItem;
+            btn.addEventListener("click", callback);
+        });
+    })();
+</script>
diff --git a/wpct-http-backend.php b/wpct-http-backend.php
index f347ca1..1017e59 100755
--- a/wpct-http-backend.php
+++ b/wpct-http-backend.php
@@ -4,7 +4,7 @@ namespace WPCT_HB;
 
 /**
  * Plugin Name:     Wpct Http Backend
- * Plugin URI:      https://git.coopdevs.org/coopdevs/website/wp/wp-plugins/wpct-odoo-connect
+ * Plugin URI:      https://git.coopdevs.org/codeccoop/wp/wpct-http-backend
  * Description:     Configure and connect WP with Bakcend over HTTP requests
  * Author:          Codec Cooperativa
  * Author URI:      https://www.codeccoop.org
@@ -23,13 +23,20 @@ if (!defined('ABSPATH')) {
 define('JWT_AUTH_SECRET_KEY', getenv('WPCT_HB_AUTH_SECRET') ? getenv('WPCT_HB_AUTH_SECRET') : '123456789');
 define('JWT_AUTH_CORS_ENABLE', true);
 
+require_once 'includes/class-singleton.php';
+require_once 'includes/class-plugin.php';
 require_once 'includes/class-menu.php';
 require_once 'includes/class-settings.php';
-require_once "includes/class-api.php";
+require_once "includes/class-http-client.php";
 
-class Plugin
+class Wpct_Http_Backend extends Plugin
 {
-    private $menu;
+    protected $name = 'Wpct Http Backed';
+    protected $textdomain = 'wpct-http-backend';
+    protected $dependencies = [
+        'JWT Authentication for WP-API' => '<a href="https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/">JWT Authentication for WP-API</a>',
+        'Wpct String Translation' => '<a href="https://git.coopdevs.org/codeccoop/wp/wpct-string-translation/">Wpct String Translation</a>',
+    ];
 
     public static function activate()
     {
@@ -58,40 +65,19 @@ class Plugin
         }
     }
 
-    public function __construct()
+    public function init()
     {
-        $settings = new Settings();
-        $this->menu = new Menu('Wpct Http Backend', $settings);
-
-        load_plugin_textdomain(
-            'wpct-http-backend',
-            false,
-            dirname(plugin_basename(__FILE__)) . '/languages',
-        );
-    }
-
-    public function on_load()
-    {
-        // Plugin dependencies
-        add_filter('wpct_dependencies_check', function ($dependencies) {
-            $dependencies['JWT Authentication for WP-API'] = '<a href="https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/">JWT Authentication for WP-API</a>';
-            $dependencies['Wpct String Translation'] = '<a href="https://git.coopdevs.org/codeccoop/wp/wpct-string-translation/">Wpct String Translation</a>';
-            return $dependencies;
-        });
-
-        $this->menu->on_load();
     }
 }
 
 register_deactivation_hook(__FILE__, function () {
-    Plugin::deactivate();
+    Wpct_Http_Backend::deactivate();
 });
 
 register_activation_hook(__FILE__, function () {
-    Plugin::activate();
+    Wpct_Http_Backend::activate();
 });
 
 add_action('plugins_loaded', function () {
-    $plugin = new Plugin();
-    $plugin->on_load();
+    $plugin = Wpct_Http_Backend::get_instance();
 }, 10);
-- 
GitLab