From 7c2f728b9b812e8a104d5d4e1cc4f3d7d235d726 Mon Sep 17 00:00:00 2001
From: Florian Bogner <f.bogner@pcsg.de>
Date: Mon, 8 May 2017 14:51:05 +0200
Subject: [PATCH] feat: Added dependency management

---
 ajax/getActivePlugins.php            |  11 -
 ajax/getPluginData.php               |  22 ++
 bin/Editor.js                        |  65 +++--
 bin/Settings.js                      |   2 +
 bin/classes/Settings.js              |  12 +-
 plugins/dependencies.json            |  89 ++++++
 src/QUI/Ckeditor/Plugins/Manager.php | 390 +++++++++++++++++++--------
 7 files changed, 424 insertions(+), 167 deletions(-)
 delete mode 100644 ajax/getActivePlugins.php
 create mode 100644 ajax/getPluginData.php
 create mode 100644 plugins/dependencies.json

diff --git a/ajax/getActivePlugins.php b/ajax/getActivePlugins.php
deleted file mode 100644
index 2e261a7..0000000
--- a/ajax/getActivePlugins.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-QUI::$Ajax->registerFunction(
-    'package_quiqqer_ckeditor4_ajax_getActivePlugins',
-    function () {
-        $Manager = new \QUI\Ckeditor\Plugins\Manager();
-
-        return $Manager->getActivePlugins();
-    },
-    array()
-);
diff --git a/ajax/getPluginData.php b/ajax/getPluginData.php
new file mode 100644
index 0000000..48c20d0
--- /dev/null
+++ b/ajax/getPluginData.php
@@ -0,0 +1,22 @@
+<?php
+
+QUI::$Ajax->registerFunction(
+    'package_quiqqer_ckeditor4_ajax_getPluginData',
+    function () {
+        $Manager = new \QUI\Ckeditor\Plugins\Manager();
+
+        // Build the web reachable path for the plugin directory
+        $pluginPath = QUI::getPackage("quiqqer/ckeditor4")->getVarDir() . "plugins";
+        $varParent = dirname(VAR_DIR);
+        $pluginPath = str_replace($varParent, "", $pluginPath);
+
+
+        $data = array(
+            'plugins'    => $Manager->getActivePlugins(),
+            'pluginPath' => $pluginPath
+        );
+
+        return $data;
+    },
+    array()
+);
diff --git a/bin/Editor.js b/bin/Editor.js
index dc1db83..1c7786c 100644
--- a/bin/Editor.js
+++ b/bin/Editor.js
@@ -213,9 +213,21 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             );
 
 
-            Settings.getPlugins().then(function (plugins) {
+            Settings.getPluginData().then(function (pluginData) {
+                var plugins    = pluginData.plugins;
+                var pluginPath = pluginData.pluginPath;
+
                 var extraPlugins = plugins.join(",");
 
+
+                for (var i = 0, len = plugins.length; i < len; i++) {
+                    var pluginName = plugins[i];
+                    if (!window.CKEDITOR.plugins.get(pluginName)) {
+                        window.CKEDITOR.plugins.addExternal(pluginName, pluginPath + "/bin/" + pluginName + "/", "");
+                    }
+                }
+
+
                 window.CKEDITOR.replace(instance, {
                     skinName           : 'moono-lisa',
                     customConfig       : '',
@@ -265,8 +277,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
                     this.$onInstanceReadyListener
                 );
             }
-        }
-        ,
+        },
 
         /**
          * event : instance ready
@@ -290,8 +301,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             this.fireEvent('loaded', [this, instance.editor]);
 
             instance.editor.focus();
-        }
-        ,
+        },
 
         /**
          * event : on resize
@@ -302,8 +312,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
                 containerSize = Container.getSize();
 
             Instance.resize(containerSize.x, containerSize.y);
-        }
-        ,
+        },
 
         /**
          * Editor onSetContent Event
@@ -315,8 +324,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             if (Editor.getInstance()) {
                 Editor.getInstance().setData(content);
             }
-        }
-        ,
+        },
 
         /**
          * Editor onGetContent Event
@@ -327,8 +335,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             if (Editor.getInstance()) {
                 Editor.setAttribute('content', Editor.getInstance().getData());
             }
-        }
-        ,
+        },
 
         /**
          * Set the focus to the editor
@@ -337,8 +344,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             if (this.getInstance()) {
                 this.getInstance().focus();
             }
-        }
-        ,
+        },
 
         /**
          * Switch to source mode
@@ -347,8 +353,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             if (this.getInstance()) {
                 this.getInstance().setMode('source');
             }
-        }
-        ,
+        },
 
         /**
          * Switch to wysiwyg editor
@@ -357,8 +362,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             if (this.getInstance()) {
                 this.getInstance().setMode('wysiwyg');
             }
-        }
-        ,
+        },
 
         /**
          * Hide the toolbar
@@ -369,8 +373,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             if (Toolbar) {
                 Toolbar.setStyle('display', 'none');
             }
-        }
-        ,
+        },
 
         /**
          * show the toolbar
@@ -381,8 +384,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             if (Toolbar) {
                 Toolbar.setStyle('display', null);
             }
-        }
-        ,
+        },
 
         /**
          * Set the height of the instance
@@ -395,8 +397,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             }
 
             this.setAttribute('height', height);
-        }
-        ,
+        },
 
         /**
          * Set the height of the instance
@@ -409,8 +410,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             }
 
             this.setAttribute('width', width);
-        }
-        ,
+        },
 
         /**
          * event : on add css
@@ -433,8 +433,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
 
                 Doc.head.appendChild(Link);
             }
-        }
-        ,
+        },
 
         /**
          * event : on Drop
@@ -446,8 +445,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             for (var i = 0, len = params.length; i < len; i++) {
                 Instance.insertHtml("<img src=" + params[i].url + " />");
             }
-        }
-        ,
+        },
 
         // /**
         //  * Generate the extra plugins option in dependence of the toolbar
@@ -683,8 +681,7 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
             };
 
             return ev;
-        }
-        ,
+        },
 
         /**
          * edit the link dialog
@@ -793,7 +790,5 @@ define('package/quiqqer/ckeditor4/bin/Editor', [
 
             return ev;
         }
-    })
-        ;
-})
-;
+    });
+});
diff --git a/bin/Settings.js b/bin/Settings.js
index 9297e93..9658910 100644
--- a/bin/Settings.js
+++ b/bin/Settings.js
@@ -1,5 +1,7 @@
 define('package/quiqqer/ckeditor4/bin/Settings', [
     'package/quiqqer/ckeditor4/bin/classes/Settings'
 ], function(Settings) {
+    "use strict";
+
     return new Settings();
 });
\ No newline at end of file
diff --git a/bin/classes/Settings.js b/bin/classes/Settings.js
index 2d7d98f..113cbe6 100644
--- a/bin/classes/Settings.js
+++ b/bin/classes/Settings.js
@@ -12,21 +12,21 @@ define('package/quiqqer/ckeditor4/bin/classes/Settings', [
         Extends: QUIDOM,
 
         initialize: function () {
-            this.plugins = null;
+            this.pluginData = null;
         },
 
 
-        getPlugins: function () {
+        getPluginData: function () {
             var self = this;
 
-            if (this.plugins !== null) {
-                return Promise.resolve(this.plugins);
+            if (this.pluginData !== null) {
+                return Promise.resolve(this.pluginData);
             }
 
             return new Promise(function (resolve, reject) {
-                QUIAjax.get("package_quiqqer_ckeditor4_ajax_getActivePlugins", function (result) {
+                QUIAjax.get("package_quiqqer_ckeditor4_ajax_getPluginData", function (result) {
 
-                    self.plugins = result;
+                    self.pluginData = result;
                     resolve(result);
                 }, {
                     'package': 'quiqqer/ckeditor4',
diff --git a/plugins/dependencies.json b/plugins/dependencies.json
new file mode 100644
index 0000000..a322df5
--- /dev/null
+++ b/plugins/dependencies.json
@@ -0,0 +1,89 @@
+{
+  "codesnippetgeshi": [
+    "ajax",
+    "codesnippet"
+  ],
+  "autolink": [
+    "ajax",
+    "clipboard"
+  ],
+  "widget": [
+    "clipboard",
+    "lineutils",
+    "widgetselection"
+  ],
+  "pastetext": [
+    "clipboard"
+  ],
+  "notification": [
+    "toolbar"
+  ],
+  "bbcode": [
+    "entities"
+  ],
+  "indentlist": [
+    "indent"
+  ],
+  "list": [
+    "indentlist"
+  ],
+  "autoembed": [
+    "undo",
+    "autolink"
+  ],
+  "dialogui": [
+    "dialog"
+  ],
+  "link": [
+    "fakeobjects",
+    "dialog"
+  ],
+  "colorbutton": [
+    "floatpanel",
+    "panelbutton"
+  ],
+  "notificationaggregator": [
+    "notification"
+  ],
+  "embedbase": [
+    "notificationaggregator",
+    "widget"
+  ],
+  "floatpanel": [
+    "panel"
+  ],
+  "panelbutton": [
+    "button"
+  ],
+  "codesnippet": [
+    "widget",
+    "embedbase"
+  ],
+  "ajax": [
+    "xml"
+  ],
+  "embed": [
+    "embedbase"
+  ],
+  "embedsemantic": [
+    "embedbase"
+  ],
+  "toolbar": [
+    "button"
+  ],
+  "a11yhelp": [
+    "dialog"
+  ],
+  "about": [
+    "dialog"
+  ],
+  "clipboard": [
+    "dialog"
+  ],
+  "dialogadvtab": [
+    "dialog"
+  ],
+  "colordialog": [
+    "dialog"
+  ]
+}
\ No newline at end of file
diff --git a/src/QUI/Ckeditor/Plugins/Manager.php b/src/QUI/Ckeditor/Plugins/Manager.php
index e6fda09..d9ece8b 100644
--- a/src/QUI/Ckeditor/Plugins/Manager.php
+++ b/src/QUI/Ckeditor/Plugins/Manager.php
@@ -4,15 +4,32 @@
 namespace QUI\Ckeditor\Plugins;
 
 use QUI\Exception;
+use QUI\System\Log;
 use QUI\Utils\Security\Orthos;
 use QUI\Utils\System\File;
 
+/**
+ * Class Manager
+ *
+ * @package QUI\Ckeditor\Plugins
+ */
 class Manager
 {
 
     protected $activePluginDir;
     protected $installedPluginDir;
 
+    protected $dependencies;
+
+    /**
+     * List of plugins which should be installed
+     * @var array
+     */
+    protected $blacklist = array(
+        "divarea",
+        "copyformatting"
+    );
+
 
     /**
      * Manager constructor.
@@ -32,85 +49,110 @@ class Manager
         }
     }
 
+    #########################################
+    #             Installation              #
+    #########################################
+
     /**
-     * Activates the given plugin name
-     *
-     * @param $pluginName
-     *
-     * @throws Exception
+     * Updates the plugins
      */
-    public function activate($pluginName)
+    public function updatePlugins()
     {
-        $pluginName = Orthos::clearPath($pluginName);
-        $pluginName = str_replace("/", "", $pluginName);
+        $srcDirs = array(
+            OPT_DIR . "ckeditor/ckeditor/plugins",
+            OPT_DIR . "quiqqer/ckeditor4/plugins/quiqqer",
+            OPT_DIR . "quiqqer/ckeditor4/plugins/ckeditor4",
 
-        if (!is_dir($this->installedPluginDir . "/" . $pluginName)) {
-            throw new Exception(array("quiqqer/ckeditor4", "exception.plugin.activate.plugin.not.found"));
-        }
+        );
 
-        if (is_dir($this->activePluginDir . "/" . $pluginName)) {
-            throw new Exception(array("quiqqer/ckeditor4", "exception.plugin.already.active"));
-        }
+        foreach ($srcDirs as $srcDir) {
+            if (!is_dir($srcDir)) {
+                return;
+            }
 
-        rename($this->installedPluginDir . "/" . $pluginName, $this->activePluginDir . "/" . $pluginName);
-    }
 
-    /**
-     * Deactivates the given plugin name
-     *
-     * @param $pluginName
-     *
-     * @throws Exception
-     */
-    public function deactivate($pluginName)
-    {
-        $pluginName = Orthos::clearPath($pluginName);
-        $pluginName = str_replace("/", "", $pluginName);
+            foreach (scandir($srcDir) as $entry) {
+                if ($entry == "." || $entry == "..") {
+                    continue;
+                }
 
-        if (!is_dir($this->activePluginDir . "/" . $pluginName)) {
-            throw new Exception(array("quiqqer/ckeditor4", "exception.plugin.activate.plugin.not.active"));
-        }
+                if (!is_dir($srcDir . "/" . $entry)) {
+                    continue;
+                }
 
-        if (is_dir($this->installedPluginDir . "/" . $pluginName)) {
-            File::deleteDir($this->activePluginDir . "/" . $pluginName);
+                # Check if/where the plugin is installed
+                $targetDir = $this->installedPluginDir . "/" . $entry;
+                if (is_dir($this->activePluginDir . "/" . $entry)) {
+                    $targetDir = $this->activePluginDir . "/" . $entry;
+                }
 
-            return;
-        }
+                if (is_dir($targetDir)) {
+                    File::deleteDir($targetDir);
+                }
 
-        rename($this->activePluginDir . "/" . $pluginName, $this->installedPluginDir . "/" . $pluginName);
+
+                File::dircopy(
+                    $srcDir . "/" . $entry,
+                    $targetDir
+                );
+            }
+        }
     }
 
     /**
-     * Returns a list of all plugins and their details
-     * Format:
-     * array(
-     *  [0] => array(
-     *      ["name"] => "pluginname",
-     *      ["state"] => 0|1  (1 for active; 0 for inactive)
-     *  )
-     * )
+     * Installs the plugins from the source packages quiqqer/ckeditor4 and ckeditor4/ckeditor4
      *
-     * @return array
      */
-    public function getAllPlugins()
+    public function installPluginsFromSource()
     {
-        $result = array();
+        $srcDirs = array(
+            OPT_DIR . "ckeditor/ckeditor/plugins",
+            OPT_DIR . "quiqqer/ckeditor4/plugins/quiqqer",
+            OPT_DIR . "quiqqer/ckeditor4/plugins/ckeditor4"
 
-        foreach ($this->getActivePlugins() as $plugin) {
-            $result[] = array(
-                'name'  => $plugin,
-                'state' => 1
-            );
+        );
+
+        foreach ($srcDirs as $srcDir) {
+            $targetDir = $this->installedPluginDir;
+
+            if (!is_dir($srcDir)) {
+                return;
+            }
+
+            foreach (scandir($srcDir) as $entry) {
+                if ($entry == "." || $entry == "..") {
+                    continue;
+                }
+
+                if (!is_dir($srcDir . "/" . $entry)) {
+                    continue;
+                }
+
+                if (is_dir($targetDir . "/" . $entry)) {
+                    continue;
+                }
+
+                if (is_dir($this->activePluginDir . "/" . $entry)) {
+                    continue;
+                }
+
+                if (in_array($entry, $this->blacklist)) {
+                    continue;
+                }
+
+                $this->copyDir(
+                    $srcDir . "/" . $entry,
+                    $targetDir . "/" . $entry
+                );
+            }
         }
 
-        foreach ($this->getInstalledPlugins() as $plugin) {
-            $result[] = array(
-                'name'  => $plugin,
-                'state' => 0
+        if (file_exists(OPT_DIR . "quiqqer/ckeditor4/plugins/dependencies.json")) {
+            copy(
+                OPT_DIR . "quiqqer/ckeditor4/plugins/dependencies.json",
+                $this->getPluginDir() . "/dependencies.json"
             );
         }
-
-        return $result;
     }
 
     /**
@@ -145,6 +187,76 @@ class Manager
         return $result;
     }
 
+    #########################################
+    #             Enable/Disable            #
+    #########################################
+
+    /**
+     * Activates the given plugin name
+     *
+     * @param $pluginName
+     *
+     * @throws Exception
+     */
+    public function activate($pluginName)
+    {
+        $pluginName = Orthos::clearPath($pluginName);
+        $pluginName = str_replace("/", "", $pluginName);
+
+        if (!is_dir($this->installedPluginDir . "/" . $pluginName)) {
+            throw new Exception(array("quiqqer/ckeditor4", "exception.plugin.activate.plugin.not.found"));
+        }
+
+        if (is_dir($this->activePluginDir . "/" . $pluginName)) {
+            throw new Exception(array("quiqqer/ckeditor4", "exception.plugin.already.active"));
+        }
+
+        $deps = $this->getDependencies($pluginName);
+        foreach ($deps as $dep) {
+            try {
+                $this->activate($dep);
+            } catch (\Exception $Exception) {
+
+            }
+
+        }
+
+        rename($this->installedPluginDir . "/" . $pluginName, $this->activePluginDir . "/" . $pluginName);
+    }
+
+    /**
+     * Deactivates the given plugin name
+     *
+     * @param $pluginName
+     *
+     * @throws Exception
+     */
+    public function deactivate($pluginName)
+    {
+        $pluginName = Orthos::clearPath($pluginName);
+        $pluginName = str_replace("/", "", $pluginName);
+
+        if (!is_dir($this->activePluginDir . "/" . $pluginName)) {
+            throw new Exception(array("quiqqer/ckeditor4", "exception.plugin.activate.plugin.not.active"));
+        }
+
+        if (is_dir($this->installedPluginDir . "/" . $pluginName)) {
+            File::deleteDir($this->activePluginDir . "/" . $pluginName);
+
+            return;
+        }
+
+        foreach ($this->getDependentPlugins($pluginName) as $depName) {
+            try {
+                $this->deactivate($depName);
+            } catch (\Exception $Exception) {
+            }
+
+        }
+
+        rename($this->activePluginDir . "/" . $pluginName, $this->installedPluginDir . "/" . $pluginName);
+    }
+
     /**
      * Returns all active plugins
      *
@@ -175,94 +287,142 @@ class Manager
         return $result;
     }
 
+    #########################################
+    #             Dependencies            #
+    #########################################
+
     /**
-     * Installs the plugins from the source packages quiqqer/ckeditor4 and ckeditor4/ckeditor4
+     * Gets all dependencies for the given plugin.
+     * Including dependencies fo dependencies
+     * Returns false on error
      *
+     * @param $pluginName
+     *
+     * @return array|false
      */
-    public function installPluginsFromSource()
+    public function getDependencies($pluginName)
     {
-        $srcDirs = array(
-            OPT_DIR . "ckeditor/ckeditor/plugins",
-            OPT_DIR . "quiqqer/ckeditor4/plugins"
+        try {
+            $this->loadDependencies();
+        } catch (\Exception $Exception) {
+            return false;
+        }
 
-        );
+        $result = array();
 
-        foreach ($srcDirs as $srcDir) {
-            $targetDir = $this->installedPluginDir;
+        if (!isset($this->dependencies[$pluginName])) {
+            return array();
+        }
 
-            if (!is_dir($srcDir)) {
-                return;
-            }
+        $deps = $this->dependencies[$pluginName];
+        foreach ($deps as $dep) {
 
-            foreach (scandir($srcDir) as $entry) {
-                if ($entry == "." || $entry == "..") {
-                    continue;
-                }
+            $result[] = $dep;
 
-                if (!is_dir($srcDir . "/" . $entry)) {
-                    continue;
-                }
+            $subDeps = $this->getDependencies($dep);
 
-                if (is_dir($targetDir . "/" . $entry)) {
-                    continue;
-                }
+            $result = array_merge($result, $subDeps);
+        }
 
-                if (is_dir($this->activePluginDir . "/" . $entry)) {
-                    continue;
-                }
+        $result = array_unique($result);
 
+        return $result;
+    }
 
-                $this->copyDir(
-                    $srcDir . "/" . $entry,
-                    $targetDir . "/" . $entry
-                );
+    /**
+     * Returns an array of packages that depend on the given plugin
+     * Returns false on error
+     *
+     * @param $pluginName
+     *
+     * @return array|bool
+     */
+    public function getDependentPlugins($pluginName)
+    {
+        $result = array();
+
+        try {
+            $this->loadDependencies();
+        } catch (\Exception $Exception) {
+            return false;
+        }
+
+
+        foreach ($this->dependencies as $pkg => $deps) {
+
+            if (in_array($pluginName, $deps)) {
+                $result[] = $pkg;
             }
         }
+
+        $result = array_unique($result);
+
+        return $result;
     }
 
     /**
-     * Updates the plugins
+     * Loads the dependencies for the installed modules
+     *
+     * @throws Exception
      */
-    public function updatePlugins()
+    protected function loadDependencies()
     {
-        $srcDirs = array(
-            OPT_DIR . "ckeditor/ckeditor/plugins",
-            OPT_DIR . "quiqqer/ckeditor4/plugins"
+        if (isset($this->dependencies) && !empty($this->dependencies)) {
+            return;
+        }
 
-        );
+        if (!file_exists($this->getPluginDir() . "/dependencies.json")) {
+            Log::addWarning("Missing dependency file: " . $this->getPluginDir() . "/dependencies.json");
 
-        foreach ($srcDirs as $srcDir) {
-            if (!is_dir($srcDir)) {
-                return;
-            }
+            throw new Exception("missing.dependency.file");
+        }
 
+        $json = file_get_contents($this->getPluginDir() . "/dependencies.json");
+        $deps = json_decode($json, true);
 
-            foreach (scandir($srcDir) as $entry) {
-                if ($entry == "." || $entry == "..") {
-                    continue;
-                }
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            throw new Exception(json_last_error_msg());
+        }
 
-                if (!is_dir($srcDir . "/" . $entry)) {
-                    continue;
-                }
+        $this->dependencies = $deps;
 
-                # Check if/where the plugin is installed
-                $targetDir = $this->installedPluginDir . "/" . $entry;
-                if (is_dir($this->activePluginDir . "/" . $entry)) {
-                    $targetDir = $this->activePluginDir . "/" . $entry;
-                }
+    }
 
-                if (is_dir($targetDir)) {
-                    File::deleteDir($targetDir);
-                }
+    #########################################
+    #               Helper                  #
+    #########################################
+
+    /**
+     * Returns a list of all plugins and their details
+     * Format:
+     * array(
+     *  [0] => array(
+     *      ["name"] => "pluginname",
+     *      ["state"] => 0|1  (1 for active; 0 for inactive)
+     *  )
+     * )
+     *
+     * @return array
+     */
+    public function getAllPlugins()
+    {
+        $result = array();
 
+        foreach ($this->getActivePlugins() as $plugin) {
+            $result[] = array(
+                'name'  => $plugin,
+                'state' => 1
+            );
+        }
 
-                File::dircopy(
-                    $srcDir . "/" . $entry,
-                    $targetDir
-                );
-            }
+        foreach ($this->getInstalledPlugins() as $plugin) {
+            $result[] = array(
+                'name'  => $plugin,
+                'state' => 0
+            );
         }
+
+        return $result;
     }
 
     /**
-- 
GitLab