pluginName = $pluginName; } public function installOrUpdatePluginFromMarketplace() { $tmpPluginZip = PIWIK_USER_PATH . self::PATH_TO_DOWNLOAD . $this->pluginName . '.zip'; $tmpPluginFolder = PIWIK_USER_PATH . self::PATH_TO_DOWNLOAD . $this->pluginName; $tmpPluginZip = SettingsPiwik::rewriteTmpPathWithHostname($tmpPluginZip); $tmpPluginFolder = SettingsPiwik::rewriteTmpPathWithHostname($tmpPluginFolder); $this->makeSureFoldersAreWritable(); $this->makeSurePluginNameIsValid(); $this->downloadPluginFromMarketplace($tmpPluginZip); $this->extractPluginFiles($tmpPluginZip, $tmpPluginFolder); $this->makeSurePluginJsonExists($tmpPluginFolder); $this->copyPluginToDestination($tmpPluginFolder); $this->removeFileIfExists($tmpPluginZip); $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)); } private function downloadPluginFromMarketplace($pluginZipTargetFile) { $this->removeFileIfExists($pluginZipTargetFile); $marketplace = new MarketplaceApiClient(); try { $marketplace->download($this->pluginName, $pluginZipTargetFile); } catch (\Exception $e) { try { $downloadUrl = $marketplace->getDownloadUrl($this->pluginName); $errorMessage = sprintf('Failed to download plugin from %s: %s', $downloadUrl, $e->getMessage()); } catch (\Exception $ex) { $errorMessage = sprintf('Failed to download plugin: %s', $e->getMessage()); } throw new PluginInstallerException($errorMessage); } } /** * @param $pluginZipFile * @param $pathExtracted * @throws \Exception */ private function extractPluginFiles($pluginZipFile, $pathExtracted) { $archive = Unzip::factory('PclZip', $pluginZipFile); $this->removeFolderIfExists($pathExtracted); if (0 == ($pluginFiles = $archive->extract($pathExtracted))) { throw new PluginInstallerException(Piwik_TranslateException('CoreUpdater_ExceptionArchiveIncompatible', $archive->errorInfo())); } if (0 == count($pluginFiles)) { throw new PluginInstallerException(Piwik_TranslateException('Plugin Zip File Is Empty')); } } private function makeSurePluginJsonExists($tmpPluginFolder) { $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, but Piwik expects plugin files to be within a subfolder in the Zip archive.'); } 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; $this->removeFolderIfExists($pluginTargetPath); Filesystem::copyRecursive($tmpPluginFolder, PIWIK_USER_PATH . self::PATH_TO_EXTRACT); } /** * @param $pathExtracted */ private function removeFolderIfExists($pathExtracted) { Filesystem::unlinkRecursive($pathExtracted, true); } /** * @param $targetTmpFile */ private function removeFileIfExists($targetTmpFile) { if (file_exists($targetTmpFile)) { unlink($targetTmpFile); } } /** * @throws PluginInstallerException */ private function makeSurePluginNameIsValid() { try { $marketplace = new MarketplaceApiClient(); $pluginDetails = $marketplace->getPluginInfo($this->pluginName); } catch (\Exception $e) { throw new PluginInstallerException($e->getMessage()); } if (empty($pluginDetails)) { throw new PluginInstallerException('This plugin was not found in the Marketplace.'); } } }