1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8namespace Tiki\Composer;
9
10use Composer\Script\Event;
11use Composer\Util\FileSystem;
12use Symfony\Component\Finder\Finder;
13
14/**
15 * After Migrate the vendors to vendors_bundled, we should clean the vendor folder
16 * We don't want to that by deleting all files in the vendor folder, instead we will try
17 * to do sensitive decisions about what to delete
18 *
19 * All the process is skipped exists a file called "do_not_clean.txt" in the vendor folder
20 *
21 * Class CleanVendorAfterVendorBundledMigration
22 * @package Tiki\Composer
23 */
24class CleanVendorAfterVendorBundledMigration
25{
26
27	// To calculate the md5 hash for the old vendor folder, on a linux server, you can use (inside the old vendor folder):
28	//
29	// $ STRING=$(ls -d */* | grep -v "^composer/" | grep -v "^bin/" | LC_COLLATE=C sort -fu | tr '\n' ':' | sed 's/:$//')
30	// $ echo -n $STRING | md5sum
31	//
32	const PRE_MIGRATION_OLD_VENDOR_FOLDER_MD5_HASH = '6997e3dc0e3ad453ab8ea9798653a0fa'; // version 17 before change
33	const VENDOR_FOLDER_MD5_HASH_16_X = '40473ceff65c1045ccd10ebd5e5e3110'; // version 16.3
34	const VENDOR_FOLDER_MD5_HASH_15_X = '273278571219f62e2d658e510684d763'; // version 15.6
35	const VENDOR_FOLDER_MD5_HASH_14_X = 'fbf3913809c5575aee178a1c2437a48a'; // version 14.4
36	const VENDOR_FOLDER_MD5_HASH_13_X = '507a38862ece4a36a6787850e7e732be'; // version 13.2
37	const VENDOR_FOLDER_MD5_HASH_12_X = '466948d920571e4065b5ddde9b0d72da'; // version 12.13
38
39	/**
40	 * @param Event $event
41	 */
42	public static function cleanLinks(Event $event)
43	{
44		self::cleanBinLinks();
45	}
46
47	/**
48	 * @param Event $event
49	 */
50	public static function clean(Event $event)
51	{
52
53		/*
54		 * 0) Make sure old bin links are removed so they can be created by composer
55		 * 1) If a file called do_not_clean.txt exists in the vendor folder stop
56		 * 2) If there is a vendor/autoload.php, check the hash of the folder structure, if different from at the time
57		 *    of the vendor_bundle migration, ignore
58		 * 2.1) Even if the hash do not match, check if 3 of the tiki bundled packages are installed, if that is the
59		 *    case warn the user as it might be a problem and disable autoload
60		 * 3) If we arrive here, clean all folders and autoload.php in the old (pre migration) vendor folder
61		 */
62
63		$io = $event->getIO();
64		$fs = new FileSystem();
65
66		$rootFolder = realpath(__DIR__ . '/../../../../');
67		$oldVendorFolder = realpath($rootFolder . '/vendor');
68
69		// 0) Make sure we can install known bin files (they might be still linked to the old vendor folder
70		self::cleanBinLinks();
71
72		// if we cant find the vendor dir no sense in progressing
73		if ($oldVendorFolder === false || ! is_dir($oldVendorFolder)) {
74			return;
75		}
76
77		// 1) If a file called do_not_clean.txt exists in the vendor folder stop
78		if (file_exists($oldVendorFolder . '/do_not_clean.txt')) {
79			$io->write('');
80			$io->write('File vendor/do_not_clean.txt is present, no attempt to clean the vendor folder will be done!');
81			$io->write('');
82
83			return;
84		}
85
86		// 2) If there is a vendor/autoload.php, check the hash of the folder structure, if different from at the time
87		//    of the vendor_bundle migration, ignore
88		if (file_exists($oldVendorFolder . '/autoload.php')) {
89			$finder = new Finder();
90			$finder->in($oldVendorFolder)->exclude(['composer', 'bin'])->depth(2);
91
92			$packages = [];
93			foreach ($finder as $file) {
94				$packages[] = $file->getRelativePath();
95			}
96
97			$packages = array_unique($packages);
98			natcasesort($packages);
99			$packagesString = implode(':', array_values($packages));
100
101			$md5checksum = md5($packagesString);
102
103			if (! in_array(
104				$md5checksum,
105				[
106					self::PRE_MIGRATION_OLD_VENDOR_FOLDER_MD5_HASH,
107					self::VENDOR_FOLDER_MD5_HASH_16_X,
108					self::VENDOR_FOLDER_MD5_HASH_15_X,
109					self::VENDOR_FOLDER_MD5_HASH_14_X,
110					self::VENDOR_FOLDER_MD5_HASH_13_X,
111					self::VENDOR_FOLDER_MD5_HASH_12_X,
112				]
113			)) {
114				// * 2.1) Even if the hash do not match, check if 3 of the tiki bundled packages are installed, if that is the
115				//        case warn the user as it might be a problem and disable autoload
116				if ((file_exists($oldVendorFolder . '/zendframework/zend-config/src/Config.php') //ZF2
117						|| file_exists($oldVendorFolder . '/bombayworks/zendframework1/library/Zend/Config.php')) //ZF1
118					&& (file_exists($oldVendorFolder . '/smarty/smarty/libs/Smarty.class.php') //Smarty
119						|| file_exists($oldVendorFolder . '/smarty/smarty/distribution/libs/Smarty.class.php')) //Smarty
120					&& file_exists($oldVendorFolder . '/adodb/adodb/adodb.inc.php') //Adodb
121				) {
122					rename($oldVendorFolder . '/autoload.php', $oldVendorFolder . '/autoload-disabled.php');
123					self::cleanTemplates($rootFolder, $fs);
124
125					$message = <<<'EOD'
126Your vendor folder contains multiple packages that were normally bundled with Tiki. Since version 17 those libraries
127were migrated from the folder "vendor" to the folder "vendor_bundled".
128
129It looks like your instance still has these libraries in the vendor folder, to avoid issues your "vendor/autoload.php"
130was renamed to "vendor/autoload-disabled.php".
131
132If you are sure that you want to use the libraries in addition to the ones bundled with tiki, please rename
133"vendor/autoload-disabled.php" back to "vendor/autoload.php" and place a file with the name "do_not_clean.txt" in the vendor folder.
134
135Tiki will not load your "vendor/autoload.php" when is detected as being a stale folder unless a file called
136"vendor/do_not_clean.txt" exists. A "vendor/do_not_clean.txt" will prevent, in future runs of composer, the automatic disabling of
137"vendor/autoload.php".
138
139Most probably you did not add your own custom libraries in addition to the ones bundled with tiki, so you can empty your "vendor" directory
140with: "rm -rf vendor/*".
141If you have your own custom libraries, you should remove all the other ones from the "vendor" directory.
142EOD;
143
144					file_put_contents($oldVendorFolder . '/autoload-disabled-README.txt', $message . "\n");
145
146					$io->write('');
147					$io->write('!!!! Warning !!!!');
148					$io->write('');
149					$io->write($message);
150					$io->write('');
151					$io->write('A copy of this information was written also into "vendor/autoload-disabled-README.txt"');
152					$io->write('');
153				}
154				return;
155			}
156		} elseif (file_exists($oldVendorFolder . '/autoload-disabled.php')) {
157			// we already disabled autoload, in previous runs, do nothing.
158			return;
159		}
160
161		// 3) If we arrive here, clean all folders and autoload.php in the old (pre migration) vendor folder
162
163		$fs->remove($oldVendorFolder . '/autoload.php');
164
165		$vendorDirsCleaned = false;
166		$vendorDirs = glob($oldVendorFolder . '/*', GLOB_ONLYDIR);
167		foreach ($vendorDirs as $dir) {
168			if (is_dir($dir)) {
169				$fs->remove($dir);
170				$vendorDirsCleaned = true;
171			}
172		}
173
174		if ($vendorDirsCleaned) {
175			self::cleanTemplates($rootFolder, $fs);
176		}
177	}
178
179	/**
180	 * Cleans links from the bin folder to the legacy vendor folder
181	 */
182	protected static function cleanBinLinks()
183	{
184		$fs = new FileSystem();
185
186		$rootFolder = realpath(__DIR__ . '/../../../../');
187		$oldVendorFolder = realpath($rootFolder . '/vendor');
188
189		// 0) Make sure we can install known bin files (they might be still linked to the old vendor folder
190		$binFiles = ['lessc', 'minifycss', 'minifyjs', 'dbunit', 'phpunit'];
191
192		foreach ($binFiles as $file) {
193			$filePath = $rootFolder . '/bin/' . $file;
194			if (is_link($filePath)) {
195				$linkDestination = readlink($filePath);
196				$fileRealPath = realpath($filePath);
197				if (strncmp($linkDestination, '../vendor/', strlen('../vendor/')) === 0 // relative link to vendor folder
198					|| $filePath === false // target don't exists, so link is broken
199					|| strncmp(
200						$fileRealPath,
201						$oldVendorFolder,
202						strlen($oldVendorFolder)
203					) === 0 // still pointing to old vendor folder
204				) {
205					$fs->unlink($filePath);
206				}
207			}
208		}
209	}
210
211	/**
212	 * Clean templates, if there was a change to the vendors
213	 * @param string $rootFolder
214	 * @param FileSystem $fs
215	 */
216	protected static function cleanTemplates($rootFolder, $fs)
217	{
218		// there are some cached templates that will stop tiki to work after the migration
219		$loopDirs = array_merge(
220			[$rootFolder . '/temp/templates_c'],
221			glob($rootFolder . '/temp/templates_c/*', GLOB_ONLYDIR)
222		);
223		foreach ($loopDirs as $dir) {
224			$cachedTemplates = glob($dir . '/*.tpl.php');
225			foreach ($cachedTemplates as $template) {
226				$fs->remove($template);
227			}
228		}
229	}
230}
231