comparison vendor/roundcube/plugin-installer/src/Roundcube/Composer/PluginInstaller.php @ 0:1e000243b222

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:50:29 -0500
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:1e000243b222
1 <?php
2
3 namespace Roundcube\Composer;
4
5 use Composer\Installer\LibraryInstaller;
6 use Composer\Package\Version\VersionParser;
7 use Composer\Semver\Constraint\Constraint;
8 use Composer\Package\PackageInterface;
9 use Composer\Repository\InstalledRepositoryInterface;
10 use Composer\Util\ProcessExecutor;
11
12 /**
13 * @category Plugins
14 * @package PluginInstaller
15 * @author Till Klampaeckel <till@php.net>
16 * @author Thomas Bruederli <thomas@roundcube.net>
17 * @license GPL-3.0+
18 * @version GIT: <git_id>
19 * @link http://github.com/roundcube/plugin-installer
20 */
21 class PluginInstaller extends LibraryInstaller
22 {
23 const INSTALLER_TYPE = 'roundcube-plugin';
24
25 /**
26 * {@inheritDoc}
27 */
28 public function getInstallPath(PackageInterface $package)
29 {
30 static $vendorDir;
31 if ($vendorDir === null) {
32 $vendorDir = $this->getVendorDir();
33 }
34
35 return sprintf('%s/%s', $vendorDir, $this->getPluginName($package));
36 }
37
38 /**
39 * {@inheritDoc}
40 */
41 public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
42 {
43 $this->rcubeVersionCheck($package);
44 parent::install($repo, $package);
45
46 // post-install: activate plugin in Roundcube config
47 $config_file = $this->rcubeConfigFile();
48 $plugin_name = $this->getPluginName($package);
49 $plugin_dir = $this->getVendorDir() . DIRECTORY_SEPARATOR . $plugin_name;
50 $extra = $package->getExtra();
51 $plugin_name = $this->getPluginName($package);
52
53 if (is_writeable($config_file) && php_sapi_name() == 'cli') {
54 $answer = $this->io->askConfirmation("Do you want to activate the plugin $plugin_name? [N|y] ", false);
55 if (true === $answer) {
56 $this->rcubeAlterConfig($plugin_name, true);
57 }
58 }
59
60 // copy config.inc.php.dist -> config.inc.php
61 if (is_file($plugin_dir . DIRECTORY_SEPARATOR . 'config.inc.php.dist') && !is_file($plugin_dir . DIRECTORY_SEPARATOR . 'config.inc.php') && is_writeable($plugin_dir)) {
62 $this->io->write("<info>Creating plugin config file</info>");
63 copy($plugin_dir . DIRECTORY_SEPARATOR . 'config.inc.php.dist', $plugin_dir . DIRECTORY_SEPARATOR . 'config.inc.php');
64 }
65
66 // initialize database schema
67 if (!empty($extra['roundcube']['sql-dir'])) {
68 if ($sqldir = realpath($plugin_dir . DIRECTORY_SEPARATOR . $extra['roundcube']['sql-dir'])) {
69 $this->io->write("<info>Running database initialization script for $plugin_name</info>");
70 system(getcwd() . "/vendor/bin/rcubeinitdb.sh --package=$plugin_name --dir=$sqldir");
71 }
72 }
73
74 // run post-install script
75 if (!empty($extra['roundcube']['post-install-script'])) {
76 $this->rcubeRunScript($extra['roundcube']['post-install-script'], $package);
77 }
78 }
79
80 /**
81 * {@inheritDoc}
82 */
83 public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
84 {
85 $this->rcubeVersionCheck($target);
86 parent::update($repo, $initial, $target);
87
88 $extra = $target->getExtra();
89
90 // trigger updatedb.sh
91 if (!empty($extra['roundcube']['sql-dir'])) {
92 $plugin_name = $this->getPluginName($target);
93 $plugin_dir = $this->getVendorDir() . DIRECTORY_SEPARATOR . $plugin_name;
94
95 if ($sqldir = realpath($plugin_dir . DIRECTORY_SEPARATOR . $extra['roundcube']['sql-dir'])) {
96 $this->io->write("<info>Updating database schema for $plugin_name</info>");
97 system(getcwd() . "/bin/updatedb.sh --package=$plugin_name --dir=$sqldir", $res);
98 }
99 }
100
101 // run post-update script
102 if (!empty($extra['roundcube']['post-update-script'])) {
103 $this->rcubeRunScript($extra['roundcube']['post-update-script'], $target);
104 }
105 }
106
107 /**
108 * {@inheritDoc}
109 */
110 public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
111 {
112 parent::uninstall($repo, $package);
113
114 // post-uninstall: deactivate plugin
115 $plugin_name = $this->getPluginName($package);
116 $this->rcubeAlterConfig($plugin_name, false);
117
118 // run post-uninstall script
119 $extra = $package->getExtra();
120 if (!empty($extra['roundcube']['post-uninstall-script'])) {
121 $this->rcubeRunScript($extra['roundcube']['post-uninstall-script'], $package);
122 }
123 }
124
125 /**
126 * {@inheritDoc}
127 */
128 public function supports($packageType)
129 {
130 return $packageType === self::INSTALLER_TYPE;
131 }
132
133 /**
134 * Setup vendor directory to one of these two:
135 * ./plugins
136 *
137 * @return string
138 */
139 public function getVendorDir()
140 {
141 $pluginDir = getcwd();
142 $pluginDir .= '/plugins';
143
144 return $pluginDir;
145 }
146
147 /**
148 * Extract the (valid) plugin name from the package object
149 */
150 private function getPluginName(PackageInterface $package)
151 {
152 @list($vendor, $pluginName) = explode('/', $package->getPrettyName());
153
154 return strtr($pluginName, '-', '_');
155 }
156
157 /**
158 * Check version requirements from the "extra" block of a package
159 * against the local Roundcube version
160 */
161 private function rcubeVersionCheck($package)
162 {
163 $parser = new VersionParser;
164
165 // read rcube version from iniset
166 $rootdir = getcwd();
167 $iniset = @file_get_contents($rootdir . '/program/include/iniset.php');
168 if (preg_match('/define\(.RCMAIL_VERSION.,\s*.([0-9.]+[a-z-]*)?/', $iniset, $m)) {
169 $rcubeVersion = $parser->normalize(str_replace('-git', '.999', $m[1]));
170 } else {
171 throw new \Exception("Unable to find a Roundcube installation in $rootdir");
172 }
173
174 $extra = $package->getExtra();
175
176 if (!empty($extra['roundcube'])) {
177 foreach (array('min-version' => '>=', 'max-version' => '<=') as $key => $operator) {
178 if (!empty($extra['roundcube'][$key])) {
179 $version = $parser->normalize(str_replace('-git', '.999', $extra['roundcube'][$key]));
180 $constraint = new Constraint($operator, $version);
181 if (!$constraint->versionCompare($rcubeVersion, $version, $operator)) {
182 throw new \Exception("Version check failed! " . $package->getName() . " requires Roundcube version $operator $version, $rcubeVersion was detected.");
183 }
184 }
185 }
186 }
187 }
188
189 /**
190 * Add or remove the given plugin to the list of active plugins in the Roundcube config.
191 */
192 private function rcubeAlterConfig($plugin_name, $add)
193 {
194 $config_file = $this->rcubeConfigFile();
195 @include($config_file);
196 $success = false;
197 $varname = '$config';
198
199 if (empty($config) && !empty($rcmail_config)) {
200 $config = $rcmail_config;
201 $varname = '$rcmail_config';
202 }
203
204 if (is_array($config) && is_writeable($config_file)) {
205 $config_templ = @file_get_contents($config_file) ?: '';
206 $config_plugins = !empty($config['plugins']) ? ((array) $config['plugins']) : array();
207 $active_plugins = $config_plugins;
208
209 if ($add && !in_array($plugin_name, $active_plugins)) {
210 $active_plugins[] = $plugin_name;
211 } elseif (!$add && ($i = array_search($plugin_name, $active_plugins)) !== false) {
212 unset($active_plugins[$i]);
213 }
214
215 if ($active_plugins != $config_plugins) {
216 $count = 0;
217 $var_export = "array(\n\t'" . join("',\n\t'", $active_plugins) . "',\n);";
218 $new_config = preg_replace(
219 "/(\\$varname\['plugins'\])\s+=\s+(.+);/Uims",
220 "\\1 = " . $var_export,
221 $config_templ, -1, $count);
222
223 // 'plugins' option does not exist yet, add it...
224 if (!$count) {
225 $var_txt = "\n{$varname}['plugins'] = $var_export;\n";
226 $new_config = str_replace('?>', $var_txt . '?>', $config_templ, $count);
227
228 if (!$count) {
229 $new_config = $config_templ . $var_txt;
230 }
231 }
232
233 $success = file_put_contents($config_file, $new_config);
234 }
235 }
236
237 if ($success && php_sapi_name() == 'cli') {
238 $this->io->write("<info>Updated local config at $config_file</info>");
239 }
240
241 return $success;
242 }
243
244 /**
245 * Helper method to get an absolute path to the local Roundcube config file
246 */
247 private function rcubeConfigFile()
248 {
249 return realpath(getcwd() . '/config/config.inc.php');
250 }
251
252 /**
253 * Run the given script file
254 */
255 private function rcubeRunScript($script, PackageInterface $package)
256 {
257 $plugin_name = $this->getPluginName($package);
258 $plugin_dir = $this->getVendorDir() . DIRECTORY_SEPARATOR . $plugin_name;
259
260 // check for executable shell script
261 if (($scriptfile = realpath($plugin_dir . DIRECTORY_SEPARATOR . $script)) && is_executable($scriptfile)) {
262 $script = $scriptfile;
263 }
264
265 // run PHP script in Roundcube context
266 if ($scriptfile && preg_match('/\.php$/', $scriptfile)) {
267 $incdir = realpath(getcwd() . '/program/include');
268 include_once($incdir . '/iniset.php');
269 include($scriptfile);
270 }
271 // attempt to execute the given string as shell commands
272 else {
273 $process = new ProcessExecutor($this->io);
274 $exitCode = $process->execute($script, null, $plugin_dir);
275 if ($exitCode !== 0) {
276 throw new \RuntimeException('Error executing script: '. $process->getErrorOutput(), $exitCode);
277 }
278 }
279 }
280 }