w:@CzucԷ/X\Jk$vR;>A0U$ tn@^S9 yz ֬ YE@Wo@/'o]!`6biKi SD@ZC+s-T zwR 2ԕACn߁ Qg*JvxͲrЗR[%ǪJ?!' `O4TfOp?%7eNH~1w"~nl oQ@,[9F1JKsk@>Xd,p-J2/.Ǝ /r7LǛiiHs=ITyC.UFZ4vK*pzq'S]΍52=3<0,Iz8|~n'$*tY@&\@"W<|}&.=0R [XuRFZmied^X0@띓gNpi6PA'ZHv )鋿 WAۧ}'hn3f--\l t(nA4eNv雬Gʭʬ/jU>W=<ϣs@F,y䓃 Xmyex <^h6H)洽ݥMNXQ wDOѬy]UUF!DT4c:v=2LLK Tm;@7kLj.sK]=N0;&%y-jΞְaOR B7!!Z3 9lg\.mo3Pҕ5=(Y5s =| ,-(nlLJ'vEk8Y"G D)TLlJeX[bx4<R-{ABhE]*~ޡdqPޘ8PM=*6F fOy;8 X_Jn)-(d-Eh(zXv"[>KAIu%gGTɃP`BECkM.?<4ث<ۿk ;ҡQ,b)+ɒjrJipk,9ƧDit̸qG*<j!2tμa1jT)Y'*ʡr3rf[(Qp V`>+ sC~cv)i\x܈ VRɊݞ  B5Cδ4џy=7#zC<;l|݀/"S۰UTW_ p l:Y-he>$5\لqɫ8Ts wH lx+j~/Y= 3`t|˺)|MY;]d7j`ERW]p"kl-(޺ه{[LQj}2 iȓd-8&ZjbAmG V$ I{$TxVOnWL' QIR:#e VXq^$nޞY~Q?7~E {GTuf:Wn =66َ)!j4m ]\WA$Y9Rȝ.Nt$G$+] \bX<.^NZh0@}p+& z1Fvy9aI*a8 0WAyMkr# 0l\3YGTjl+,^(}{/nwgFN5K]Od|@DG0h3sR禧s-%]JLa©wHLT%(Dr FKҝ[ _VA)wgFN5K]O RKCph;d*Z ֕*m'Ϣ[IZgO1Rb5xގ{+(-C >ǶUO6*h jTABK}Y:f~:!w8Hx}Y{<4 P?EzgLkl baZmvox6~h8]mr= ldXu%ި1ܛss8~ՄnzD :jEWR,^-7jum՘GyEiF)Iq;PBPwgFN5K]OvYpz,;|ަ?q!&: Ȃ-N1Bx5p $4@Km`wgFN5K]Oǀ}NFJ4 wT$W^-8Ӊ7D?8lHx"zAwgFN5K]OL8KʥTjw"wgFN5K]OMb{^F xMy oJy4T_Ԇ_oR2\ "ʟDi1 ;UtYu+e{ȷg^wgFN5K]O`Tm+}y>0(w?rT)Z#BPa Z7ݥ0rGɂߌ5"KfSX5ɏpL'栧zD/dby8*y܇!>f[+,T̞%r"KJuFwl`8]1#_'0W9Ǐ(Dx'*ҜI잠Qn'|n$WϜvq J]l@ uKCL⮎ ĀY U%8H { QUWoy+ѻRyG)ynJK &.Ulȋc͇P|u{y<*a֬ M/OKҸ{Z3y3,` x炗;Il") , Su.dҞ'P"#'/Ԝ.#`pTq&RIu'v Di\ Jhk3XnkҀ=e{X^`Gڎ95A!r>ɔ`ͅ[rcubeVersionCheck($target); $self = $this; $extra = $target->getExtra(); $fs = new Filesystem(); // backup persistent files e.g. config.inc.php $package_name = $self->getPackageName($initial); $package_dir = $self->getVendorDir() . DIRECTORY_SEPARATOR . $package_name; $temp_dir = $package_dir . '-' . sprintf('%010d%010d', mt_rand(), mt_rand()); // make a backup of existing files (for restoring persistent files) $fs->copy($package_dir, $temp_dir); $postUpdate = function() use ($self, $target, $extra, $fs, $temp_dir) { $package_name = $self->getPackageName($target); $package_dir = $self->getVendorDir() . DIRECTORY_SEPARATOR . $package_name; // restore persistent files $persistent_files = !empty($extra['roundcube']['persistent-files']) ? $extra['roundcube']['persistent-files'] : ['config.inc.php']; foreach ($persistent_files as $file) { $path = $temp_dir . DIRECTORY_SEPARATOR . $file; if (is_readable($path)) { if ($fs->copy($path, $package_dir . DIRECTORY_SEPARATOR . $file)) { $self->io->write("Restored $package_name/$file"); } else { throw new \Exception("Restoring " . $file . " failed."); } } } // remove backup folder $fs->remove($temp_dir); // update database schema if (!empty($extra['roundcube']['sql-dir'])) { if ($sqldir = realpath($package_dir . DIRECTORY_SEPARATOR . $extra['roundcube']['sql-dir'])) { $self->io->write("Updating database schema for $package_name"); $roundcube_version = self::versionNormalize(RCMAIL_VERSION); if (self::versionCompare($roundcube_version, '1.2.0', '>=')) { \rcmail_utils::db_update($sqldir, $package_name); } else { throw new \Exception("Database update failed. Roundcube 1.2.0 or above required."); } } } // run post-update script if (!empty($extra['roundcube']['post-update-script'])) { $self->rcubeRunScript($extra['roundcube']['post-update-script'], $target); } }; $promise = parent::update($repo, $initial, $target); // Composer v2 might return a promise here if ($promise instanceof PromiseInterface) { return $promise->then($postUpdate); } // If not, execute the code right away (composer v1, or v2 without async) $postUpdate(); } /** * {@inheritDoc} */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { // initialize Roundcube environment if (!defined('INSTALL_PATH')) { define('INSTALL_PATH', getcwd() . '/'); } include_once(INSTALL_PATH . 'program/include/clisetup.php'); $self = $this; $config = $self->composer->getConfig()->get('roundcube'); $postUninstall = function() use ($self, $package, $config) { // post-uninstall: deactivate package $package_name = $self->getPackageName($package); $package_dir = $self->getVendorDir() . DIRECTORY_SEPARATOR . $package_name; $self->rcubeAlterConfig($package_name, false); // run post-uninstall script $extra = $package->getExtra(); if (!empty($extra['roundcube']['post-uninstall-script'])) { $self->rcubeRunScript($extra['roundcube']['post-uninstall-script'], $package); } // remove package folder if (!empty($config['uninstall-remove-folder'])) { $fs = new Filesystem(); $fs->remove($package_dir); $self->io->write("Removed $package_name files"); } }; $promise = parent::uninstall($repo, $package); // Composer v2 might return a promise here if ($promise instanceof PromiseInterface) { return $promise->then($postUninstall); } // If not, execute the code right away (composer v1, or v2 without async) $postUninstall(); } /** * {@inheritDoc} */ public function supports($packageType) { return $packageType === $this->composer_type; } /** * Setup vendor directory to one of these two: * * @return string */ public function getVendorDir() { return getcwd(); } /** * Extract the (valid) package name from the package object */ protected function getPackageName(PackageInterface $package) { @list($vendor, $packageName) = explode('/', $package->getPrettyName()); return strtr($packageName, '-', '_'); } /** * Check version requirements from the "extra" block of a package * against the local Roundcube version */ private function rcubeVersionCheck($package) { // read rcube version from iniset $rcubeVersion = self::versionNormalize(RCMAIL_VERSION); if (empty($rcubeVersion)) { throw new \Exception("Unable to find a Roundcube installation in $rootdir"); } $extra = $package->getExtra(); if (!empty($extra['roundcube'])) { foreach (['min-version' => '>=', 'max-version' => '<='] as $key => $operator) { if (!empty($extra['roundcube'][$key])) { $version = self::versionNormalize($extra['roundcube'][$key]); if (!self::versionCompare($rcubeVersion, $version, $operator)) { throw new \Exception("Version check failed! " . $package->getName() . " requires Roundcube version $operator $version, $rcubeVersion was detected."); } } } } } /** * Add or remove the given package to the Roundcube config. */ private function rcubeAlterConfig($package_name, $add = true) { $config_file = $this->rcubeConfigFile(); @include($config_file); $success = false; $varname = '$config'; if (empty($config) && !empty($rcmail_config)) { $config = $rcmail_config; $varname = '$rcmail_config'; } if (!empty($config) && is_writeable($config_file)) { $config_template = @file_get_contents($config_file) ?: ''; if ($config = $this->getConfig($package_name, $config, $add)) { list($config_name, $config_val) = $config; $count = 0; if (empty($config_val)) { $new_config = preg_replace( "/(\\$varname\['$config_name'\])\s+=\s+(.+);/Uims", "", $config_template, -1, $count); } else { $new_config = preg_replace( "/(\\$varname\['$config_name'\])\s+=\s+(.+);/Uims", "\\1 = " . $config_val, $config_template, -1, $count); } // config option does not exist yet, add it... if (!$count) { $var_txt = "\n{$varname}['$config_name'] = $config_val\n"; $new_config = str_replace('?>', $var_txt . '?>', $config_template, $count); if (!$count) { $new_config = $config_template . $var_txt; } } $success = file_put_contents($config_file, $new_config); } } if ($success && php_sapi_name() == 'cli') { $this->io->write("Updated local config at $config_file"); } return $success; } /** * Ask the user to confirm installation */ protected function confirmInstall($package_name) { return false; } /** * Generate Roundcube config entry */ protected function getConfig($package_name, $cur_config, $add) { return false; } /** * Helper method to get an absolute path to the local Roundcube config file */ private function rcubeConfigFile($file = 'config.inc.php') { $config = new \rcube_config(); $paths = $config->resolve_paths($file); $path = getcwd() . '/config/' . $file; foreach ($paths as $fpath) { if ($fpath && is_file($fpath) && is_readable($fpath)) { $path = $fpath; break; } } return realpath($path); } /** * Run the given script file */ private function rcubeRunScript($script, PackageInterface $package) { $package_name = $this->getPackageName($package); $package_type = $package->getType(); $package_dir = $this->getVendorDir() . DIRECTORY_SEPARATOR . $package_name; // check for executable shell script if (($scriptfile = realpath($package_dir . DIRECTORY_SEPARATOR . $script)) && is_executable($scriptfile)) { $script = $scriptfile; } // run PHP script in Roundcube context if ($scriptfile && preg_match('/\.php$/', $scriptfile)) { $incdir = realpath(getcwd() . '/program/include'); include_once($incdir . '/iniset.php'); include($scriptfile); } // attempt to execute the given string as shell commands else { $process = new ProcessExecutor($this->io); $exitCode = $process->execute($script, $output, $package_dir); if ($exitCode !== 0) { throw new \RuntimeException('Error executing script: '. $process->getErrorOutput(), $exitCode); } } } /** * normalize Roundcube version string */ private static function versionNormalize($version) { $parser = new VersionParser; return $parser->normalize(str_replace('-git', '.999', $version)); } /** * version_compare() wrapper, originally from composer/semver */ private static function versionCompare($a, $b, $operator, $compareBranches = false) { $aIsBranch = 'dev-' === substr($a, 0, 4); $bIsBranch = 'dev-' === substr($b, 0, 4); if ($aIsBranch && $bIsBranch) { return $operator === '==' && $a === $b; } // when branches are not comparable, we make sure dev branches never match anything if (!$compareBranches && ($aIsBranch || $bIsBranch)) { return false; } return version_compare($a, $b, $operator); } }