├── .gitignore
├── Source
├── Commands
│ ├── RenamespaceCommand.php
│ └── ScanUsesCommand.php
├── Models
│ ├── Configuration.php
│ ├── Package.php
│ └── Project.php
└── functions.php
├── bin
└── namespacer
├── composer.json
├── patches.config.php
├── readme.md
├── sample.composer.json
└── sample.config.php
/.gitignore:
--------------------------------------------------------------------------------
1 | plugin
2 | dest
3 | build
4 | temp
5 | vendor
6 | composer.lock
7 |
--------------------------------------------------------------------------------
/Source/Commands/RenamespaceCommand.php:
--------------------------------------------------------------------------------
1 | rootDir = trailingslashit($rootDir);
23 | }
24 |
25 | protected function configure() {
26 | $this->setDescription("Renamespaces a composer.json project for use in WordPress plugins.");
27 |
28 | $this->addArgument("dest", InputArgument::REQUIRED, "The path to save the renamespaced libraries to.");
29 |
30 | $this->addOption("composer", null, InputOption::VALUE_REQUIRED, "The path to the composer.json containing the packages to renamespace.", null);
31 | $this->addOption("source", null, InputOption::VALUE_REQUIRED, "The path to the directory containing the composer.json to renamespace. This directory should already have had `composer update` run in it.", null);
32 |
33 | $this->addOption("package", null, InputOption::VALUE_REQUIRED, "The prefix to add to packages", "mcloud");
34 | $this->addOption("namespace", null, InputOption::VALUE_REQUIRED, "The prefix to add to namespaces", "MediaCloud\\Vendor\\");
35 |
36 | $this->addOption("config", null, InputOption::VALUE_REQUIRED, "The path to the configuration to use, if required.", null);
37 | }
38 |
39 | protected function execute(InputInterface $input, OutputInterface $output) {
40 | //region Directories
41 |
42 | if (empty($input->getOption('composer')) && empty($input->getOption('source'))) {
43 | $output->writeln("You must specify either the --composer or --source option.");
44 | return Command::FAILURE;
45 | }
46 |
47 | $packagePrefix = $input->getOption('package');
48 | $namespacePrefix = $input->getOption('namespace');
49 |
50 | $namespacePrefix = rtrim($namespacePrefix, "\\")."\\";
51 |
52 | $tempPath = trailingslashit($this->rootDir.uniqid());
53 | @mkdir($tempPath, 0755, true);
54 |
55 | if (!empty($input->getOption('composer'))) {
56 | $originalComposer = $input->getOption('composer');
57 | if (strpos($originalComposer, '/') !== 0) {
58 | $originalComposer = $this->rootDir.$originalComposer;
59 | } else {
60 | $originalComposer = $originalComposer;
61 | }
62 |
63 | if (!file_exists($originalComposer)) {
64 | rmdir($tempPath);
65 | $output->writeln("Composer file does not exist at $originalComposer.");
66 | return Command::FAILURE;
67 | }
68 |
69 | $sourcePath = trailingslashit($tempPath.'project');
70 | @mkdir($sourcePath, 0755, true);
71 |
72 | copy($originalComposer, $sourcePath.'composer.json');
73 |
74 | $output->writeln("Creating project ... ");
75 | $output->writeln("");
76 | `cd $sourcePath && composer update`;
77 | $output->writeln("");
78 | } else {
79 | $sourcePath = $input->getOption('source');
80 | if (strpos($sourcePath, '/') !== 0) {
81 | $sourcePath = trailingslashit($this->rootDir.$sourcePath);
82 | } else {
83 | $sourcePath = trailingslashit($sourcePath);
84 | }
85 |
86 | if (!file_exists($sourcePath)) {
87 | $output->writeln("Input directory $sourcePath does not exist.");
88 | return Command::FAILURE;
89 | }
90 | }
91 |
92 | $outputPath = $input->getArgument('dest');
93 | if (strpos($outputPath, '/') !== 0) {
94 | $outputPath = trailingslashit($this->rootDir.$outputPath);
95 | } else {
96 | $outputPath = trailingslashit($outputPath);
97 | }
98 |
99 | if (file_exists($outputPath)) {
100 | if (file_exists($outputPath.'lib')) {
101 | `rm -rf {$outputPath}lib`;
102 | }
103 | } else {
104 | if(!mkdir($outputPath, 0755, true)) {
105 | $output->writeln("Could not create output directory.");
106 | return Command::FAILURE;
107 | }
108 | }
109 |
110 | $projectOutputPath = $tempPath.'build/';
111 | $libraryOutputPath = $tempPath.'library/';
112 |
113 | @mkdir($projectOutputPath, 0755, true);
114 | @mkdir($libraryOutputPath, 0755, true);
115 |
116 | $configPath = null;
117 | if (!empty($input->getOption('config'))) {
118 | $configPath = $input->getOption('config');
119 | if (strpos($configPath, '/') !== 0) {
120 | $configPath = $this->rootDir.$configPath;
121 | }
122 |
123 | if (!file_exists($configPath)) {
124 | $output->writeln("Config file $configPath does not exist.");
125 | return Command::FAILURE;
126 | }
127 | }
128 |
129 | //endregion
130 |
131 | //region Project Info
132 |
133 | $project = new Project($sourcePath);
134 |
135 | $output->writeln("");
136 |
137 | $table = new Table($output);
138 | $table
139 | ->setHeaderTitle("Settings")
140 | ->setHeaders(['Setting', 'Value'])
141 | ->setRows([
142 | ["Package Prefix", $packagePrefix],
143 | ["Namespace Prefix", $namespacePrefix],
144 | ["Source", $sourcePath],
145 | ["Destination", $outputPath],
146 | ["Config", $configPath],
147 | ])
148 | ->render();
149 |
150 | $output->writeln("");
151 |
152 | $table = new Table($output);
153 | $table
154 | ->setHeaderTitle("Found Packages")
155 | ->setHeaders(['Package', 'Version'])
156 | ->setRows($project->getPackageTableData())
157 | ->render();
158 |
159 | $output->writeln("");
160 |
161 | //endregion
162 |
163 | //region Package Processing
164 | $configuration = new Configuration($configPath);
165 |
166 | $packageSection = $output->section();
167 | $packageSection->writeln("Processing packages ...");
168 |
169 | $packageProgressSection = $output->section();
170 | $packageProgress = new ProgressBar($packageProgressSection, count($project->getPackages()));
171 | $packageProgress->setBarWidth(50);
172 | $packageProgress->start();
173 |
174 | $sourceFileCount = 0;
175 | $allNamespaces = [];
176 | foreach($project->getPackages() as $packageName => $package) {
177 | $packageSection->overwrite("Processing package $packageName ...");
178 | $packageProgress->advance();
179 | $package->process($configuration, $packagePrefix, $namespacePrefix, $libraryOutputPath, $project->getPackages());
180 | $allNamespaces = array_merge($allNamespaces, $package->getNamespaces());
181 | $sourceFileCount += count($package->getSourceFiles());
182 | }
183 |
184 | $packageSection->overwrite("Finished processing packages.");
185 | $packageProgress->finish();
186 |
187 | $packageProgressSection->clear();
188 |
189 | $output->writeln("");
190 | $output->writeln("Found ".count($allNamespaces)." namespaces in {$sourceFileCount} source files.");
191 |
192 | //endregion
193 |
194 | //region Re-namespacing
195 |
196 | $packageSection = $output->section();
197 | $packageSection->writeln("Re-namespacing packages ...");
198 |
199 | $packageProgressSection = $output->section();
200 | $packageProgress = new ProgressBar($packageProgressSection, $sourceFileCount);
201 | $packageProgress->setFormat('very_verbose');
202 | $packageProgress->setBarWidth(50);
203 | $packageProgress->start();
204 |
205 | foreach($project->getPackages() as $packageName => $package) {
206 | $packageSection->overwrite("Re-namespacing package $packageName ...");
207 | $packageProgress->advance();
208 | $package->renamespace($configuration, $packageSection, $packageProgress, $namespacePrefix, $allNamespaces);
209 | }
210 |
211 | $packageSection->overwrite("Finished re-namespacing packages.");
212 | $packageProgress->finish();
213 |
214 | $packageProgressSection->clear();
215 |
216 | $output->writeln("");
217 |
218 | //endregion
219 |
220 | //region Finished
221 |
222 | $project->save($projectOutputPath, $packagePrefix);
223 | $output->writeln("Saved project. Running composer ...");
224 | $output->writeln("");
225 |
226 | `cd {$projectOutputPath} && composer update`;
227 | `rm -rf {$projectOutputPath}vendor/bin`;
228 | rename($projectOutputPath.'vendor', $outputPath.'lib');
229 | `rm -rf $tempPath`;
230 |
231 | $output->writeln("");
232 | $output->writeln("Finished.");
233 |
234 | //endregion
235 |
236 | return Command::SUCCESS;
237 | }
238 | }
--------------------------------------------------------------------------------
/Source/Commands/ScanUsesCommand.php:
--------------------------------------------------------------------------------
1 | rootDir = trailingslashit($rootDir);
21 | }
22 |
23 | protected function configure() {
24 | $this->addArgument("input", InputArgument::REQUIRED, "The path to the directory containing the PHP files to scan.");
25 | }
26 |
27 | protected function execute(InputInterface $input, OutputInterface $output) {
28 | //region Directories
29 | $sourcePath = $input->getArgument('input');
30 | if (strpos($sourcePath, '/') !== 0) {
31 | $sourcePath = trailingslashit($this->rootDir.$sourcePath);
32 | } else {
33 | $sourcePath = trailingslashit($sourcePath);
34 | }
35 |
36 | if (!file_exists($sourcePath)) {
37 | $output->writeln("Input directory $sourcePath does not exist.");
38 | return Command::FAILURE;
39 | }
40 |
41 | $finder = new Finder();
42 | $finder
43 | ->followLinks()
44 | ->name("*.php")
45 | ->name("*.inc");
46 |
47 | $sourceFiles = [];
48 | /** @var SplFileInfo $file */
49 | foreach($finder->in(trailingslashit($sourcePath)) as $file) {
50 | $sourceFiles[] = $file->getRealPath();
51 | }
52 |
53 |
54 | $output->writeln("Found ".count($sourceFiles)." source files.");
55 | $uses = [];
56 | foreach($sourceFiles as $sourceFile) {
57 | $source = file_get_contents($sourceFile);
58 | preg_match_all('#^\s*use\s+([^;]+)#m', $source, $matches, PREG_SET_ORDER);
59 | if (count($matches) > 0) {
60 | foreach($matches as $match) {
61 | if (!in_array($match[1], $uses)) {
62 | if (strpos($match[1], 'function ') === 0) {
63 | continue;
64 | }
65 |
66 | $uses[] = $match[1];
67 | }
68 | }
69 | }
70 | }
71 |
72 | sort($uses);
73 |
74 | $output->writeln("");
75 | $output->writeln($uses);
76 | $output->writeln("");
77 | $output->writeln("Finished.");
78 |
79 | return Command::SUCCESS;
80 | }
81 | }
--------------------------------------------------------------------------------
/Source/Models/Configuration.php:
--------------------------------------------------------------------------------
1 | config = include $configFile;
11 | }
12 | }
13 |
14 | public function prepare(string $package, array $config, string $path, string $namespacePrefix) {
15 | if (isset($this->config['prepare'])) {
16 | foreach($this->config['prepare'] as $func) {
17 | $config = call_user_func($func, $package, $config, $path, $namespacePrefix);
18 | }
19 | }
20 |
21 | return $config;
22 | }
23 |
24 | public function start(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
25 | if (isset($this->config['start'])) {
26 | foreach($this->config['start'] as $func) {
27 | $source = call_user_func($func, $source, $currentNamespace, $namespacePrefix, $package, $file);
28 | }
29 | }
30 |
31 | return $source;
32 | }
33 |
34 | public function before(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
35 | if (isset($this->config['before'])) {
36 | foreach($this->config['before'] as $func) {
37 | $source = call_user_func($func, $source, $namespace, $currentNamespace, $namespacePrefix, $package, $file);
38 | }
39 | }
40 |
41 | return $source;
42 | }
43 |
44 | public function after(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
45 | if (isset($this->config['after'])) {
46 | foreach($this->config['after'] as $func) {
47 | $source = call_user_func($func, $source, $namespace, $currentNamespace, $namespacePrefix, $package, $file);
48 | }
49 | }
50 |
51 | return $source;
52 | }
53 |
54 | public function end(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
55 | if (isset($this->config['end'])) {
56 | foreach($this->config['end'] as $func) {
57 | $source = call_user_func($func, $source, $currentNamespace, $namespacePrefix, $package, $file);
58 | }
59 | }
60 |
61 | return $source;
62 | }
63 | }
--------------------------------------------------------------------------------
/Source/Models/Package.php:
--------------------------------------------------------------------------------
1 | name = $name;
31 | $this->path = trailingslashit($path);
32 | $this->version = $version;
33 | }
34 |
35 | //region Properties
36 |
37 | /**
38 | * @return string
39 | */
40 | public function getName() {
41 | return $this->name;
42 | }
43 |
44 | /**
45 | * @return string
46 | */
47 | public function getPath() {
48 | return $this->path;
49 | }
50 |
51 | /**
52 | * @return string
53 | */
54 | public function getVersion(): string {
55 | return $this->version;
56 | }
57 |
58 | /**
59 | * @return array
60 | */
61 | public function getNamespaces(): array {
62 | return $this->namespaces;
63 | }
64 |
65 | /**
66 | * @return array
67 | */
68 | public function getSourceFiles(): array {
69 | return $this->sourceFiles;
70 | }
71 |
72 | //endregion
73 |
74 | //region Processing
75 |
76 | /**
77 | * @param Configuration $configuration
78 | * @param string $packagePrefix
79 | * @param string $namespacePrefix
80 | * @param string $outputPath
81 | * @param Package[] $packages
82 | */
83 | public function process($configuration, string $packagePrefix, string $namespacePrefix, string $outputPath, array $packages) {
84 | $outputPath = trailingslashit($outputPath);
85 | $this->outputPath = $outputPath;
86 |
87 | $composerFile = $this->path.'composer.json';
88 | if (!file_exists($composerFile)) {
89 | throw new \Exception("Missing composer.json for package {$this->name}.");
90 | }
91 |
92 | $config = json_decode(file_get_contents($composerFile), true);
93 |
94 | $config = $configuration->prepare($this->name, $config, $this->path, $namespacePrefix);
95 |
96 | $config['name'] = $packagePrefix.'-'.$config['name'];
97 | $config['version'] = $this->getVersion();
98 |
99 | if (isset($config['require'])) {
100 | foreach($config['require'] as $packageName => $version) {
101 | if (strpos($packageName, 'ext-') === 0) {
102 | continue;
103 | }
104 |
105 | if (strpos($packageName, 'lib-') === 0) {
106 | continue;
107 | }
108 |
109 | if ($packageName === 'php') {
110 | continue;
111 | }
112 |
113 | if ($packageName === 'composer-plugin-api') {
114 | unset($config['require'][$packageName]);
115 | continue;
116 | }
117 |
118 | if ($packageName === 'kylekatarnls/update-helper') {
119 | unset($config['require'][$packageName]);
120 | continue;
121 | }
122 |
123 | if (!isset($packages[$packageName])) {
124 | throw new \Exception("Cannot find related package $packageName for {$this->name}.");
125 | }
126 |
127 | $package = $packages[$packageName];
128 | unset($config['require'][$packageName]);
129 | $config['require'][$packagePrefix.'-'.$packageName] = $package->getVersion();
130 | }
131 | }
132 |
133 | if (isset($config['autoload'])) {
134 | if (isset($config['autoload']['psr-0'])) {
135 | foreach($config['autoload']['psr-0'] as $namespace => $directory) {
136 | $tempPsr0Path = $this->path.'tmp/';
137 | $psr0Path = trailingslashit($this->path.$directory);
138 | mkdir($tempPsr0Path, 0755, true);
139 | `mv {$psr0Path}* $tempPsr0Path`;
140 |
141 | $namespacePath = ltrim(str_replace("\\", "/", $namespacePrefix), '\\');
142 | $newPsr0Path = trailingslashit(trailingslashit($this->path.$directory).$namespacePath);
143 | mkdir($newPsr0Path, 0755, true);
144 | `mv {$tempPsr0Path}* $newPsr0Path`;
145 | @rmdir($tempPsr0Path);
146 |
147 | $config['autoload']['psr-0'][$namespacePrefix.$namespace] = $directory;
148 | unset($config['autoload']['psr-0'][$namespace]);
149 | }
150 | }
151 |
152 | if (isset($config['autoload']['psr-4'])) {
153 | foreach($config['autoload']['psr-4'] as $namespace => $directory) {
154 | $config['autoload']['psr-4'][$namespacePrefix.$namespace] = $directory;
155 | unset($config['autoload']['psr-4'][$namespace]);
156 | }
157 | }
158 | }
159 |
160 | unset($config['extra']['branch-alias']['dev-master']);
161 |
162 | @mkdir("$outputPath{$this->name}", 0755, true);
163 | `cp -r {$this->path} $outputPath{$this->name}/`;
164 |
165 | file_put_contents($outputPath.$this->name.'/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
166 |
167 | $finder = new Finder();
168 | $finder
169 | ->followLinks()
170 | ->name("*.php")
171 | ->name("*.inc");
172 |
173 | $this->sourceFiles = [];
174 | /** @var SplFileInfo $file */
175 | foreach($finder->in(trailingslashit($outputPath.$this->name))->exclude('composer') as $file) {
176 | $this->sourceFiles[] = $file->getRealPath();
177 | }
178 |
179 | $namespaceRegex = '/^\s*namespace\s+([^\s;]+)/m';
180 | $idiotNamespaceRegex = '/^\s*\<\?php\s+namespace\s+([^\s;]+)/m';
181 | foreach($this->sourceFiles as $file) {
182 | $matches = [];
183 | preg_match_all($namespaceRegex, file_get_contents($file), $matches, PREG_SET_ORDER, 0);
184 | if (count($matches) > 0) {
185 | foreach($matches as $match) {
186 | if ($match[1] == "'.__NAMESPACE__.'") {
187 | continue;
188 | }
189 |
190 | if (!in_array($match[1], $this->namespaces)) {
191 | $this->namespaces[] = $match[1];
192 | }
193 | }
194 | } else {
195 | preg_match_all($idiotNamespaceRegex, file_get_contents($file), $matches, PREG_SET_ORDER, 0);
196 | if (count($matches) > 0) {
197 | foreach($matches as $match) {
198 | if ($match[1] == "'.__NAMESPACE__.'") {
199 | continue;
200 | }
201 |
202 | if (!in_array($match[1], $this->namespaces)) {
203 | $this->namespaces[] = $match[1];
204 | }
205 | }
206 | }
207 | }
208 | }
209 | }
210 |
211 | /**
212 | * @param Configuration $configuration
213 | * @param ConsoleSectionOutput $output
214 | * @param ProgressBar $progressBar
215 | * @param string $namespacePrefix
216 | * @param array $namespaces
217 | */
218 | public function renamespace($configuration, $output, $progressBar, string $namespacePrefix, array $namespaces) {
219 | $namespacePrefixString = str_replace("\\", "\\\\", $namespacePrefix);
220 | $namespacePrefixStringRegex = str_replace("\\", "\\\\", $namespacePrefixString);
221 |
222 | foreach($this->sourceFiles as $file) {
223 | if (!empty($output) && !empty($this->outputPath)) {
224 | $relativeFile = str_replace($this->outputPath.$this->name, '', $file);
225 | $output->overwrite("Re-namespacing package {$this->name} ... $relativeFile");
226 | }
227 |
228 | if (!empty($progressBar)) {
229 | $progressBar->advance();
230 | }
231 |
232 | $source = file_get_contents($file);
233 |
234 | $currentNamespace = null;
235 | preg_match("#^\s*namespace\s+([^;]+)#m", $source, $matches);
236 | if (count($matches) > 1) {
237 | $currentNamespace = $matches[1];
238 | } else {
239 | preg_match("#^\s*\<\?php\s+namespace\s+([^;]+)#m", $source, $matches);
240 | if (count($matches) > 1) {
241 | $currentNamespace = $matches[1];
242 | }
243 | }
244 |
245 | $source = $configuration->start($source, $currentNamespace, $namespacePrefix, $this->name, $file);
246 |
247 | $currentNamespaceRegexSafe = str_replace("\\","\\\\", $currentNamespace);
248 | $source = preg_replace("#^\s*namespace\s+$currentNamespaceRegexSafe\s*;#m", "\nnamespace $namespacePrefix$currentNamespace;", $source, -1, $count);
249 | $source = preg_replace("#^\s*\<\?php\s+namespace\s+$currentNamespaceRegexSafe\s*;#m", "before($source, $namespace, $currentNamespace, $namespacePrefix, $this->name, $file);
254 |
255 | $namespace = $namespace."\\";
256 | $stringNamespace = str_replace("\\", "\\\\", $namespace);
257 | $stringNamespaceRegex = str_replace("\\", "\\\\", $stringNamespace);
258 |
259 | $namespaceTrimmed = rtrim($namespace, "\\");
260 | $stringNamespaceTrimmed = rtrim(str_replace("\\", "\\\\", $namespace), "\\");
261 |
262 |
263 | $source = preg_replace("#^\\s*use\\s+$stringNamespace#m", "use $namespacePrefix$namespace", $source, -1, $count);
264 | $changes += $count;
265 |
266 | $source = preg_replace("#^\\s*use\\s+$stringNamespaceTrimmed;#m", "use $namespacePrefix$namespaceTrimmed;", $source, -1, $count);
267 | $changes += $count;
268 |
269 | $source = preg_replace("#\\s+$stringNamespaceRegex#m", " $namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count);
270 | $changes += $count;
271 |
272 | $source = preg_replace("#\\\"$stringNamespaceRegex#m", "\"$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count);
273 | $changes += $count;
274 |
275 | $source = preg_replace("#\\'$stringNamespaceRegex#m", "'$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count);
276 | $changes += $count;
277 |
278 | $source = preg_replace("#\\s+\\\\\\\\$stringNamespaceRegex#m", " \\\\$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count);
279 | $changes += $count;
280 |
281 | $source = preg_replace("#\\\"\\\\\\\\$stringNamespaceRegex#m", "\"\\\\$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count);
282 | $changes += $count;
283 |
284 | $source = preg_replace("#\\'\\\\\\\\$stringNamespaceRegex#m", "'\\\\$namespacePrefixStringRegex$stringNamespace\\", $source, -1, $count);
285 | $changes += $count;
286 |
287 | $source = preg_replace("#\\s+$stringNamespace#m", " $namespacePrefix$namespace", $source, -1, $count);
288 | $changes += $count;
289 |
290 | $source = preg_replace("#\\\"$stringNamespace#m", "\"$namespacePrefix$namespace", $source, -1, $count);
291 | $changes += $count;
292 |
293 | $source = preg_replace("#\\'$stringNamespace#m", "'$namespacePrefix$namespace", $source, -1, $count);
294 | $changes += $count;
295 |
296 | $source = preg_replace("#\\'\\\\$stringNamespace#m", "'\\$namespacePrefix$namespace", $source, -1, $count);
297 | $changes += $count;
298 |
299 | $source = preg_replace("#\\(\s*\\\\$stringNamespace#m", "(\\$namespacePrefix$namespace", $source, -1, $count);
300 | $changes += $count;
301 |
302 | $source = preg_replace("#\\s+\\\\$stringNamespace#m", " \\$namespacePrefix$namespace", $source, -1, $count);
303 | $changes += $count;
304 |
305 | $source = preg_replace("#\\s+$namespacePrefixString(.*)\s*\(#m", ' \\NAMESPACEPLACEHOLDER$1(', $source, -1, $count);
306 | $source = str_replace('NAMESPACEPLACEHOLDER', $namespacePrefix, $source);
307 | $changes += $count;
308 |
309 | $source = $configuration->after($source, $namespace, $currentNamespace, $namespacePrefix, $this->name, $file);
310 | }
311 |
312 | $source = $configuration->end($source, $currentNamespace, $namespacePrefix, $this->name, $file);
313 |
314 | file_put_contents($file, $source);
315 | }
316 | }
317 |
318 | //endregion
319 |
320 | }
--------------------------------------------------------------------------------
/Source/Models/Project.php:
--------------------------------------------------------------------------------
1 | sourcePath = trailingslashit($sourcePath);
19 |
20 | $composerLock = $sourcePath.'composer.lock';
21 | if (!file_exists($composerLock)) {
22 | throw new \Exception("Composer lock file missing.");
23 | }
24 |
25 | $lockConfig = json_decode(file_get_contents($composerLock), true);
26 | if (!isset($lockConfig['packages'])) {
27 | throw new \Exception("No installed packages.");
28 | }
29 |
30 | foreach($lockConfig['packages'] as $package) {
31 | $packageName = $package['name'];
32 | $version = $package['version'];
33 | $path = trailingslashit($sourcePath.'vendor/'.$packageName);
34 |
35 | $package = new Package($packageName, $path, $version);
36 | $this->packages[$packageName] = $package;
37 | $this->packageTableData[] = [$packageName, $version];
38 | }
39 |
40 | $composerFile = $sourcePath.'composer.json';
41 | if (!file_exists($composerFile)) {
42 | throw new \Exception("Composer file missing.");
43 | }
44 |
45 | $this->config = json_decode(file_get_contents($composerFile), true);
46 | }
47 |
48 | /**
49 | * @return array
50 | */
51 | public function getPackages(): array {
52 | return $this->packages;
53 | }
54 |
55 | /**
56 | * @return array
57 | */
58 | public function getPackageTableData(): array {
59 | return $this->packageTableData;
60 | }
61 |
62 | public function addRepository(string $packageName) {
63 | $this->config['repositories'][] = [
64 | 'type' => 'path',
65 | 'url' => '../library/'.$packageName,
66 | 'options' => [
67 | 'symlink' => false
68 | ]
69 | ];
70 | }
71 |
72 | public function save(string $savePath, string $packagePrefix) {
73 | $composerFile = trailingslashit($savePath).'composer.json';
74 |
75 | foreach($this->packages as $packageName => $package) {
76 | $this->addRepository($packageName);
77 | }
78 |
79 | if (isset($this->config['require'])) {
80 | foreach($this->config['require'] as $packageName => $version) {
81 | if (strpos($packageName, $packagePrefix.'-') === 0) {
82 | continue;
83 | }
84 |
85 | if (strpos($packageName, 'ext-') === 0) {
86 | continue;
87 | }
88 |
89 | if (strpos($packageName, 'lib-') === 0) {
90 | continue;
91 | }
92 |
93 | if ($packageName === 'php') {
94 | continue;
95 | }
96 |
97 | if ($packageName === 'composer-plugin-api') {
98 | unset($this->config['require'][$packageName]);
99 | continue;
100 | }
101 |
102 | if (!isset($this->packages[$packageName])) {
103 | continue;
104 | }
105 |
106 | $package = $this->packages[$packageName];
107 | unset($this->config['require'][$packageName]);
108 | $this->config['require'][$packagePrefix.'-'.$packageName] = $package->getVersion();
109 | }
110 | }
111 |
112 | file_put_contents($composerFile, json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
113 | }
114 | }
--------------------------------------------------------------------------------
/Source/functions.php:
--------------------------------------------------------------------------------
1 | add($renamespaceCommand);
26 | $application->add($scanCommand);
27 | $application->setDefaultCommand('renamespace', true);
28 | $application->run();
29 |
30 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ilab/namespacer",
3 | "description": "Tool for re-namespacing composer packages for use in WordPress plugins.",
4 | "type": "library",
5 | "license": "LGPL-3.0-or-later",
6 | "keywords": [
7 | "namespace",
8 | "wordpress"
9 | ],
10 | "authors": [
11 | {
12 | "name": "Jon Gilkison",
13 | "email": "jon@interfacelab.com"
14 | }
15 | ],
16 | "require": {
17 | "symfony/finder": "^5.1",
18 | "symfony/console": "^5.1",
19 | "ext-json": "*"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "ILAB\\Namespacer\\": "Source"
24 | },
25 | "files": [
26 | "Source/functions.php"
27 | ]
28 | },
29 | "bin": ["bin/namespacer"]
30 | }
31 |
--------------------------------------------------------------------------------
/patches.config.php:
--------------------------------------------------------------------------------
1 | [
5 | function(string $package, array $config, string $path, string $namespacePrefix) {
6 | if (($package == 'kraken-io/kraken-php') && isset($config['autoload']['psr-0']['Kraken'])) {
7 | $srcDir = trailingslashit($path.$config['autoload']['psr-0']['Kraken']);
8 | $finder = new Symfony\Component\Finder\Finder();
9 | $files = [];
10 | foreach($finder->in($srcDir) as $fileInfo) {
11 | $files[] = $fileInfo->getRealPath();
12 | }
13 |
14 | if (file_exists($srcDir.'Kraken.php')) {
15 | $source = file_get_contents($srcDir.'Kraken.php');
16 | $source = str_replace(' [
32 | function(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
33 | if ($package === 'duncan3dc/blade') {
34 | $filename = pathinfo($file, PATHINFO_BASENAME);
35 | if ($filename == 'BladeInstance.php') {
36 | $source = str_replace("private function getViewFinder(", "protected function getViewFinder(", $source);
37 | $source = str_replace("private function getViewFactory(", "protected function getViewFactory(", $source);
38 | }
39 | } else if ($package === 'smalot/pdfparser') {
40 | $filename = pathinfo($file, PATHINFO_BASENAME);
41 | if ($filename == 'Font.php') {
42 | $source = str_replace('$details[\'Encoding\'] = ($this->has(\'Encoding\') ? (string) $this->get(\'Encoding\') : \'Ansi\');', '$details[\'Encoding\'] = \'Ansi\';', $source);
43 | }
44 | }
45 |
46 | return $source;
47 | },
48 | ],
49 | ];
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Namespacer
2 | Using composer when building WordPress plugins sound like a good idea at first, but it won't be long until you run into
3 | trouble when other plugins are loading incompatible versions of libraries that you are using. Our plugin,
4 | [MediaCloud](https://mediacloud.press), uses packages extensively - over 65 different libraries - many of them fairly
5 | common. I'd say over half of our support requests are due to conflicts with other plugins users have installed that
6 | are using outdated or newer versions of the same libraries we are using. The only way to really do that is to
7 | re-namespace the packages we are using and to be able to do that easily and safely.
8 |
9 | Namespacer allows you to re-namespace any composer packages you are using by adding a namespace prefix to all of the
10 | namespaces as well as prefixing the package names too. It will then generate a folder called "lib" which you can safely
11 | include in your plugin.
12 |
13 | Yes, I'm aware of projects out there like PHP-Scoper and Imposter and others. I've had issues with all of them, which
14 | is why I built this. Yoast and other plugins happily used PHP-Scoper, but their plugins, believe it or not, are much
15 | smaller than [MediaCloud](https://mediacloud.press). Yoast, for example, uses 4 composer packages.
16 |
17 | ## Installation
18 | You can install this globally, but I think you'd be better off using it as the basis of a project via composer.
19 |
20 | ```bash
21 | composer require ilab/namespacer
22 | ```
23 |
24 | ## Usage
25 | Once installed:
26 |
27 | ```bash
28 | ./vendor/bin/renamespace [--composer COMPOSER] [--source SOURCE] [--package PACKAGE] [--namespace NAMESPACE] [--config CONFIG]
29 | ```
30 |
31 | ### Arguments
32 | | Argument | Description |
33 | | ----------- | ----------- |
34 | | `composer` | The path (full or relative) to the composer file containing all the package dependencies you want to renamespace. You must specify this argument OR the `source` argument. |
35 | | `source` | The path (full or relative) to a directory containing a composer file and an existing vendor directory. When using `source` the vendor directory must already exist (`composer update` must already have been run). You must specify this argument OR the `composer` argument. |
36 | | `package` | The prefix to append to package names, for example specifying `--package mcloud` will turn `nesbot/carbon` into `mcloud-nesbot/carbon`. Default is `mcloud`. |
37 | | `namespace` | The prefix to append to namespaces, for example specifying `--namespace MediaCloud\Vendor` will transform `namespace Aws;` into `namespace MediaCloud\Vendor\Aws;`. Default is `MediaClound\Vendor`. |
38 | | `config` | An optional PHP configuration file for inserting filters into the namespacing process. |
39 | | `` | The destination directory. Namespacer will create a directory named `lib` inside that directory, removing it first if it already exists. |
40 |
41 | For example, you might run it:
42 |
43 | ```bash
44 | ./vendor/bin/namespacer --composer sample.composer.json --config patches.config.php --package mypackage --namespace MyNamespace\Vendor build/
45 | ```
46 |
47 | In this example, we're pointing to a `composer.json` file containing the packages we want to re-namespace and to a
48 | config file that contains filters that will apply more manual patches during the re-namespace process. The output
49 | of the processing will be put into the `build/` folder.
50 |
51 | ### Filtering (Patching in PHP-Scoper parlance)
52 | You can see some example configurations in `vendor/ilab/namespacer/sample.config.php` and
53 | `vendor/ilab/namespacer/patches.config.php` that will demonstrate how to insert your own code into the namespacing
54 | process to catch special cases.
55 |
56 | ## Reporting Bugs
57 | If you run into issues, please open a ticket and attach the composer.json you were trying to process with a clear
58 | description of the problem.
--------------------------------------------------------------------------------
/sample.composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "ilab/ilab-media-tools",
3 | "description": "Tools for cropping, uploading to S3, serving through Imgix",
4 | "type": "wordpress-plugin",
5 | "license": "LGPL-3.0-or-later",
6 | "keywords": ["s3","imgix","cropping", "wordpress"],
7 | "repositories": [
8 | ],
9 | "authors": [
10 | {
11 | "name": "Jon Gilkison",
12 | "email": "jon@interfacelab.com"
13 | }
14 | ],
15 | "config": {
16 | "platform": {
17 | "php": "7.1"
18 | }
19 | },
20 | "require" : {
21 | "php": ">=7.1",
22 | "duncan3dc/blade": ">=3.3",
23 | "fasterimage/fasterimage": ">=1.1",
24 | "google/cloud-storage": ">=1.21",
25 | "google/cloud-vision": ">=0.19.0",
26 | "guzzlehttp/guzzle": ">=6.3.3",
27 | "ilab/b2-sdk-php": ">=1.4",
28 | "aws/aws-sdk-php": ">=3.0",
29 | "imgix/imgix-php": ">=1.0",
30 | "mikey179/vfsstream": "*",
31 | "monolog/monolog": ">=1.23",
32 | "psr/http-message-implementation": "*",
33 | "ralouphie/mimey": ">=2.1",
34 | "smalot/pdfparser": ">=0.13.2",
35 | "trntv/probe": ">=1.0",
36 | "zumba/amplitude-php": ">=1.0",
37 | "paragonie/easyrsa": ">=0.5.2",
38 | "kraken-io/kraken-php": ">=1.6",
39 | "shortpixel/shortpixel-php": ">=1.6",
40 | "wp-media/imagify-php": ">=1.1",
41 | "tinify/tinify": ">=1.5",
42 | "firebase/php-jwt": ">=5.2",
43 | "muxinc/mux-php": ">=0.4.0",
44 | "ivopetkov/html5-dom-document-php": ">=2.2",
45 | "dragonmantank/cron-expression": ">=2.3",
46 | "lorisleiva/cron-translator": ">=0.1.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/sample.config.php:
--------------------------------------------------------------------------------
1 | [
6 | function(string $package, array $config, string $path, string $namespacePrefix) {
7 | /**
8 | * @var string $package The name of the composer package
9 | * @var array $config The parsed composer.json config for the package
10 | * @var string $path The full path to the package
11 | * @var string $namespacePrefix The namespace prefix
12 | */
13 |
14 | return $config; // You should always return the $config after manipulating it
15 | }
16 | ],
17 |
18 | /** These functions are called once the source file has been loaded but before all of the namespace changes are processed. */
19 | "start" => [
20 | function(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
21 | /**
22 | * @var string $source The PHP source file contents
23 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix)
24 | * @var string $namespacePrefix The new namespace prefix
25 | * @var string $package The name of the composer package
26 | * @var string $file The complete path to the source file
27 | */
28 | return $source; // You should always return the $source after manipulating it
29 | },
30 | ],
31 |
32 | /** These functions are called once per namespace being processed before the regex's are run. */
33 | "before" => [
34 | function(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
35 | /**
36 | * @var string $source The PHP source file contents
37 | * @var string $namespace The namespace currently being processed
38 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix)
39 | * @var string $namespacePrefix The new namespace prefix
40 | * @var string $package The name of the composer package
41 | * @var string $file The complete path to the source file
42 | */
43 | return $source; // You should always return the $source after manipulating it
44 | }
45 | ],
46 |
47 | /** These functions are called once per namespace being processed after the regex's are run. */
48 | "after" => [
49 | function(string $source, string $namespace, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
50 | /**
51 | * @var string $source The PHP source file contents
52 | * @var string $namespace The namespace currently being processed
53 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix)
54 | * @var string $namespacePrefix The new namespace prefix
55 | * @var string $package The name of the composer package
56 | * @var string $file The complete path to the source file
57 | */
58 | return $source; // You should always return the $source after manipulating it
59 | }
60 | ],
61 |
62 | /** These functions are called before the changed source file is saved, after all the processing has taken place. */
63 | "end" => [
64 | function(string $source, ?string $currentNamespace, string $namespacePrefix, string $package, string $file) {
65 | /**
66 | * @var string $source The PHP source file contents
67 | * @var string|null $currentNamespace The current namespace of the source file (without new prefix)
68 | * @var string $namespacePrefix The new namespace prefix
69 | * @var string $package The name of the composer package
70 | * @var string $file The complete path to the source file
71 | */
72 | return $source; // You should always return the $source after manipulating it
73 | },
74 | ]
75 | ];
--------------------------------------------------------------------------------