Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Steur <thomas.steur@gmail.com>2013-10-01 05:05:54 +0400
committerThomas Steur <thomas.steur@gmail.com>2013-10-01 05:05:54 +0400
commite58b0d1efc28225f5599946a40fefbee20f5a74f (patch)
tree708eb2fa7340543d7874899ee4baa89752a0dd29
parent58233e38185173cfd7a44656c9560e2a95ede3aa (diff)
refs #607 added possibility to install a plugin or theme by uploading a ZIP file
-rw-r--r--plugins/CorePluginsAdmin/Controller.php43
-rw-r--r--plugins/CorePluginsAdmin/CorePluginsAdmin.php1
-rw-r--r--plugins/CorePluginsAdmin/PluginInstaller.php98
-rw-r--r--plugins/CorePluginsAdmin/javascripts/pluginExtend.js20
-rw-r--r--plugins/CorePluginsAdmin/stylesheets/marketplace.less12
-rw-r--r--plugins/CorePluginsAdmin/templates/extend.twig28
-rw-r--r--plugins/CorePluginsAdmin/templates/uploadPlugin.twig44
7 files changed, 241 insertions, 5 deletions
diff --git a/plugins/CorePluginsAdmin/Controller.php b/plugins/CorePluginsAdmin/Controller.php
index cf83b013e1..a5ca8c74f4 100644
--- a/plugins/CorePluginsAdmin/Controller.php
+++ b/plugins/CorePluginsAdmin/Controller.php
@@ -73,6 +73,47 @@ class Controller extends \Piwik\Controller\Admin
echo $view->render();
}
+ public function uploadPlugin()
+ {
+ Piwik::checkUserIsSuperUser();
+
+ $nonce = Common::getRequestVar('nonce', null, 'string');
+
+ if (!Nonce::verifyNonce(static::INSTALL_NONCE, $nonce)) {
+ throw new \Exception(Piwik_Translate('General_ExceptionNonceMismatch'));
+ }
+
+ Nonce::discardNonce(static::INSTALL_NONCE);
+
+ if (empty($_FILES['pluginZip'])) {
+ throw new \Exception('You did not specify a ZIP file.');
+ }
+
+ if (!empty($_FILES['pluginZip']['error'])) {
+ throw new \Exception('Something went wrong during the plugin file upload. Please try again.');
+ }
+
+ $file = $_FILES['pluginZip']['tmp_name'];
+ if (!file_exists($file)) {
+ throw new \Exception('Something went wrong during the plugin file upload. Please try again.');
+ }
+
+ $view = $this->configureView('@CorePluginsAdmin/uploadPlugin');
+
+ $pluginInstaller = new PluginInstaller('uploaded');
+ $pluginMetadata = $pluginInstaller->installOrUpdatePluginFromFile($file);
+
+ $view->nonce = Nonce::getNonce(static::ACTIVATE_NONCE);
+ $view->plugin = array(
+ 'name' => $pluginMetadata->name,
+ 'version' => $pluginMetadata->version,
+ 'isTheme' => !empty($pluginMetadata->theme),
+ 'isActivated' => PluginsManager::getInstance()->isPluginActivated($pluginMetadata->name)
+ );
+
+ echo $view->render();
+ }
+
public function pluginDetails()
{
$pluginName = Common::getRequestVar('pluginName', null, 'string');
@@ -127,6 +168,8 @@ class Controller extends \Piwik\Controller\Admin
function extend()
{
$view = $this->configureView('@CorePluginsAdmin/extend');
+ $view->installNonce = Nonce::getNonce(static::INSTALL_NONCE);
+
echo $view->render();
}
diff --git a/plugins/CorePluginsAdmin/CorePluginsAdmin.php b/plugins/CorePluginsAdmin/CorePluginsAdmin.php
index 53bbdf0a76..6fb999a1ce 100644
--- a/plugins/CorePluginsAdmin/CorePluginsAdmin.php
+++ b/plugins/CorePluginsAdmin/CorePluginsAdmin.php
@@ -90,6 +90,7 @@ class CorePluginsAdmin extends \Piwik\Plugin
$jsFiles[] = "plugins/CoreHome/javascripts/popover.js";
$jsFiles[] = "plugins/CorePluginsAdmin/javascripts/pluginDetail.js";
$jsFiles[] = "plugins/CorePluginsAdmin/javascripts/pluginOverview.js";
+ $jsFiles[] = "plugins/CorePluginsAdmin/javascripts/pluginExtend.js";
}
}
diff --git a/plugins/CorePluginsAdmin/PluginInstaller.php b/plugins/CorePluginsAdmin/PluginInstaller.php
index ff58b45d9c..5f634f048e 100644
--- a/plugins/CorePluginsAdmin/PluginInstaller.php
+++ b/plugins/CorePluginsAdmin/PluginInstaller.php
@@ -11,6 +11,7 @@
namespace Piwik\Plugins\CorePluginsAdmin;
use Piwik\Filechecks;
use Piwik\Filesystem;
+use Piwik\Log;
use Piwik\SettingsPiwik;
use Piwik\Unzip;
@@ -49,6 +50,27 @@ class PluginInstaller
$this->removeFolderIfExists($tmpPluginFolder);
}
+ public function installOrUpdatePluginFromFile($pathToZip)
+ {
+ $tmpPluginFolder = PIWIK_USER_PATH . self::PATH_TO_DOWNLOAD . $this->pluginName;
+ $tmpPluginFolder = SettingsPiwik::rewriteTmpPathWithHostname($tmpPluginFolder);
+
+ $this->makeSureFoldersAreWritable();
+ $this->extractPluginFiles($pathToZip, $tmpPluginFolder);
+
+ $this->makeSurePluginJsonExists($tmpPluginFolder);
+ $metadata = $this->getPluginMetadataIfValid($tmpPluginFolder);
+
+ $this->pluginName = $metadata->name;
+
+ $this->fixPluginFolderIfNeeded($tmpPluginFolder);
+ $this->copyPluginToDestination($tmpPluginFolder);
+ $this->removeFileIfExists($pathToZip);
+ $this->removeFolderIfExists($tmpPluginFolder);
+
+ return $metadata;
+ }
+
private function makeSureFoldersAreWritable()
{
Filechecks::dieIfDirectoriesNotWritable(array(self::PATH_TO_DOWNLOAD, self::PATH_TO_EXTRACT));
@@ -98,11 +120,85 @@ class PluginInstaller
private function makeSurePluginJsonExists($tmpPluginFolder)
{
- if (!file_exists($tmpPluginFolder . DIRECTORY_SEPARATOR . $this->pluginName . DIRECTORY_SEPARATOR . 'plugin.json')) {
+ $pluginJsonPath = $this->getPathToPluginJson($tmpPluginFolder);
+
+ if (!file_exists($pluginJsonPath)) {
throw new PluginInstallerException('Plugin is not valid, it is missing the plugin.json file.');
}
}
+ private function getPluginMetadataIfValid($tmpPluginFolder)
+ {
+ $pluginJsonPath = $this->getPathToPluginJson($tmpPluginFolder);
+
+ $metadata = file_get_contents($pluginJsonPath);
+ $metadata = json_decode($metadata);
+
+ if (empty($metadata)) {
+ throw new PluginInstallerException('Plugin is not valid, plugin.json is empty or does not contain valid JSON.');
+ }
+
+ if (empty($metadata->name)) {
+ throw new PluginInstallerException('Plugin is not valid, the plugin.json file does not specify the plugin name.');
+ }
+
+ if (empty($metadata->version)) {
+ throw new PluginInstallerException('Plugin is not valid, the plugin.json file does not specify the plugin version.');
+ }
+
+ if (empty($metadata->description)) {
+ throw new PluginInstallerException('Plugin is not valid, the plugin.json file does not specify a description.');
+ }
+
+ return $metadata;
+ }
+
+ private function getPathToPluginJson($tmpPluginFolder)
+ {
+ $firstSubFolder = $this->getNameOfFirstSubfolder($tmpPluginFolder);
+ $path = $tmpPluginFolder . DIRECTORY_SEPARATOR . $firstSubFolder . DIRECTORY_SEPARATOR . 'plugin.json';
+
+ return $path;
+ }
+
+ /**
+ * @param $pluginDir
+ * @throws PluginInstallerException
+ * @return string
+ */
+ private function getNameOfFirstSubfolder($pluginDir)
+ {
+ if ($dir = opendir($pluginDir)) {
+ $firstSubFolder = '';
+
+ while ($file = readdir($dir)) {
+ if ($file[0] != '.' && is_dir($pluginDir . DIRECTORY_SEPARATOR . $file)) {
+ $firstSubFolder = $file;
+ break;
+ }
+ }
+
+ if (empty($firstSubFolder)) {
+ throw new PluginInstallerException('The plugin ZIP file does not contain a subfolder.');
+ }
+
+ return $firstSubFolder;
+ }
+ }
+
+ private function fixPluginFolderIfNeeded($tmpPluginFolder)
+ {
+ $firstSubFolder = $this->getNameOfFirstSubfolder($tmpPluginFolder);
+
+ if ($firstSubFolder === $this->pluginName) {
+ return;
+ }
+
+ $from = $tmpPluginFolder . DIRECTORY_SEPARATOR . $firstSubFolder;
+ $to = $tmpPluginFolder . DIRECTORY_SEPARATOR . $this->pluginName;
+ rename($from, $to);
+ }
+
private function copyPluginToDestination($tmpPluginFolder)
{
$pluginTargetPath = PIWIK_USER_PATH . self::PATH_TO_EXTRACT . $this->pluginName;
diff --git a/plugins/CorePluginsAdmin/javascripts/pluginExtend.js b/plugins/CorePluginsAdmin/javascripts/pluginExtend.js
new file mode 100644
index 0000000000..1063a179f8
--- /dev/null
+++ b/plugins/CorePluginsAdmin/javascripts/pluginExtend.js
@@ -0,0 +1,20 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+$(document).ready(function () {
+
+ $('.extendPlatform .uploadPlugin').click(function (event) {
+ event.preventDefault();
+
+ piwikHelper.modalConfirm('#installPluginByUpload', {
+ yes: function () {
+ window.location = link;
+ }
+ });
+ });
+
+}); \ No newline at end of file
diff --git a/plugins/CorePluginsAdmin/stylesheets/marketplace.less b/plugins/CorePluginsAdmin/stylesheets/marketplace.less
index 7c6ba22bb7..653d255051 100644
--- a/plugins/CorePluginsAdmin/stylesheets/marketplace.less
+++ b/plugins/CorePluginsAdmin/stylesheets/marketplace.less
@@ -9,6 +9,18 @@
.callToAction { font-size: 1.1em;line-height: 2em; }
}
+#installPluginByUpload {
+ .description {
+ margin-top: 30px;
+ margin-bottom: 20px;
+ }
+
+ .startUpload {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }
+}
+
.pluginslist {
margin-top: 20px;
max-width: 980px;
diff --git a/plugins/CorePluginsAdmin/templates/extend.twig b/plugins/CorePluginsAdmin/templates/extend.twig
index 0b04efb308..a45ec1de2a 100644
--- a/plugins/CorePluginsAdmin/templates/extend.twig
+++ b/plugins/CorePluginsAdmin/templates/extend.twig
@@ -5,6 +5,19 @@
{% block content %}
<div class="extendPlatform">
+ <div class="ui-confirm" id="installPluginByUpload">
+ <h2>Extend Piwik by uploading a ZIP file</h2>
+
+ <p class="description">You may upload here a ZIP downloaded from the Piwik Marketplace or your own plugin or theme.</p>
+
+ <form enctype="multipart/form-data"
+ method="post"
+ action="{{ linkTo({'action':'uploadPlugin', 'nonce': installNonce}) }}">
+ <input type="file" name="pluginZip"><br />
+ <input class="startUpload" type="submit" value="Upload ZIP file">
+ </form>
+ </div>
+
<div class="introduction">
<h2>Extend Piwik with Plugins and Themes</h2>
@@ -21,24 +34,31 @@
<div>
<div class="byPlugins">
<h3 class="header">Get new functionality</h3>
- <span class="callToAction">by <a href="{{ linkTo({'action':'browsePlugins', 'sort': ''}) }}">installing a new plugin</a></span>
+ <span class="callToAction">by <a href="{{ linkTo({'action':'browsePlugins', 'sort': ''}) }}">installing a new plugin from the Marketplace</a></span>
<p>
<a href="{{ linkTo({'action':'browsePlugins', 'sort': ''}) }}"><img class="teaserImage" alt="Install a new plugin" src=""/></a>
</p>
- <span class="callToAction">or <a href="#">write your own plugin</a></span>
+ <span class="callToAction">
+ by <a href="#">writing your own plugin</a>
+ <br/> or by <a href="#" class="uploadPlugin">uploading a plugin</a>
+ </span>
+
</div>
<div class="byThemes">
<h3 class="header">Enjoy another look & feel</h3>
- <span class="callToAction">by <a href="{{ linkTo({'action':'browseThemes', 'sort': ''}) }}">installing a new theme</a></span>
+ <span class="callToAction">by <a href="{{ linkTo({'action':'browseThemes', 'sort': ''}) }}">installing a new theme from the Marketplace</a></span>
<p>
<a href="{{ linkTo({'action':'browseThemes', 'sort': ''}) }}"><img class="teaserImage" alt="Install a new theme" src=""/></a>
</p>
- <span class="callToAction">or <a href="#">design your own theme</a></span>
+ <span class="callToAction">
+ by <a href="#">design your own theme</a><br />
+ or by <a href="#" class="uploadPlugin">uploading a theme</a>
+ </span>
</div>
</div>
</div>
diff --git a/plugins/CorePluginsAdmin/templates/uploadPlugin.twig b/plugins/CorePluginsAdmin/templates/uploadPlugin.twig
new file mode 100644
index 0000000000..ae4a611a38
--- /dev/null
+++ b/plugins/CorePluginsAdmin/templates/uploadPlugin.twig
@@ -0,0 +1,44 @@
+{% extends 'admin.twig' %}
+
+{% block content %}
+
+ <div style="max-width:980px;">
+
+ <div>
+ <h2>Installing {{ plugin.name }}</h2>
+
+ {% if plugin.isTheme %}
+
+ <p>Unzipping theme</p>
+
+ <p>You have successfully installed the theme {{ plugin.name }} {{ plugin.version }}.</p>
+
+ <p>
+ {% if not plugin.isActivated %}
+ <strong><a href="{{ linkTo({'action': 'activate', 'pluginName': plugin.name, 'nonce': nonce}) }}">Activate Theme</a></strong>
+
+ |
+ {% endif %}
+ <a href="{{ linkTo({'action': 'extend'}) }}">Back to Extend Piwik</a>
+ </p>
+
+ {% else %}
+
+ <p>Unzipping plugin</p>
+
+ <p>You have successfully installed the Plugin {{ plugin.name }} {{ plugin.version }}.</p>
+
+ <p>
+ {% if not plugin.isActivated %}
+ <strong><a href="{{ linkTo({'action': 'activate', 'pluginName': plugin.name, 'nonce': nonce}) }}">Activate Plugin</a></strong>
+
+ |
+ {% endif %}
+ <a href="{{ linkTo({'action': 'extend'}) }}">Back to Extend Piwik</a>
+ </p>
+
+ {% endif %}
+ </div>
+ </div>
+
+{% endblock %}