These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | use SilverStripe\SsPak\DataExtractor\DatabaseConnector; |
||
4 | use SilverStripe\SsPak\DataExtractor\CsvTableWriter; |
||
5 | use SilverStripe\SsPak\DataExtractor\CsvTableReader; |
||
6 | |||
7 | /** |
||
8 | * SSPak handler |
||
9 | */ |
||
10 | class SSPak { |
||
11 | protected $executor; |
||
12 | |||
13 | /** |
||
14 | * Create a new handler |
||
15 | * @param Executor $executor The Executor object to handle command execution |
||
16 | */ |
||
17 | function __construct($executor) { |
||
18 | $this->executor = $executor; |
||
19 | } |
||
20 | |||
21 | function getActions() { |
||
22 | return array( |
||
23 | "help" => array( |
||
24 | "description" => "Show this help message.", |
||
25 | "method" => "help", |
||
26 | ), |
||
27 | "save" => array( |
||
28 | "description" => "Save an .sspak file from a SilverStripe site.", |
||
29 | "unnamedArgs" => array("webroot", "sspak file"), |
||
30 | "namedArgs" => array("identity"), |
||
31 | "method" => "save", |
||
32 | ), |
||
33 | "load" => array( |
||
34 | "description" => "Load an .sspak file into a SilverStripe site. Does not backup - be careful!", |
||
35 | "unnamedArgs" => array("sspak file", "[webroot]"), |
||
36 | "namedArgs" => array("identity"), |
||
37 | "namedFlags" => array("drop-db"), |
||
38 | "method" => "load", |
||
39 | ), |
||
40 | "saveexisting" => array( |
||
41 | "description" => "Create an .sspak file from database SQL dump and/or assets. Does not require a SilverStripe site.", |
||
42 | "unnamedArgs" => array("sspak file"), |
||
43 | "namedArgs" => array("db", "assets"), |
||
44 | "method" => "saveexisting" |
||
45 | ), |
||
46 | "extract" => array( |
||
47 | "description" => "Extract an .sspak file into the current working directory. Does not require a SilverStripe site.", |
||
48 | "unnamedArgs" => array("sspak file", "destination path"), |
||
49 | "method" => "extract" |
||
50 | ), |
||
51 | "listtables" => array( |
||
52 | "description" => "List tables in the database", |
||
53 | "unnamedArgs" => array("webroot"), |
||
54 | "method" => "listTables" |
||
55 | ), |
||
56 | |||
57 | "savecsv" => array( |
||
58 | "description" => "Save tables in the database to a collection of CSV files", |
||
59 | "unnamedArgs" => array("webroot", "output-path"), |
||
60 | "method" => "saveCsv" |
||
61 | ), |
||
62 | |||
63 | "loadcsv" => array( |
||
64 | "description" => "Load tables from collection of CSV files to a webroot", |
||
65 | "unnamedArgs" => array("input-path", "webroot"), |
||
66 | "method" => "loadCsv" |
||
67 | ), |
||
68 | /* |
||
69 | |||
70 | "install" => array( |
||
71 | "description" => "Install a .sspak file into a new environment.", |
||
72 | "unnamedArgs" => array("sspak file", "new webroot"), |
||
73 | "method" => "install", |
||
74 | ), |
||
75 | "bundle" => array( |
||
76 | "description" => "Bundle a .sspak file into a self-extracting executable .sspak.phar installer.", |
||
77 | "unnamedArgs" => array("sspak file", "executable"), |
||
78 | "method" => "bundle", |
||
79 | ), |
||
80 | "transfer" => array( |
||
81 | "description" => "Transfer db & assets from one site to another (not implemented yet).", |
||
82 | "unnamedArgs" => array("src webroot", "dest webroot"), |
||
83 | "method" => "transfer", |
||
84 | ), |
||
85 | */ |
||
86 | ); |
||
87 | } |
||
88 | |||
89 | function help($args) { |
||
90 | echo "SSPak: manage SilverStripe .sspak archives.\n\nUsage:\n"; |
||
91 | foreach($this->getActions() as $action => $info) { |
||
92 | echo "sspak $action"; |
||
93 | if(!empty($info['unnamedArgs'])) { |
||
94 | foreach($info['unnamedArgs'] as $arg) echo " ($arg)"; |
||
0 ignored issues
–
show
|
|||
95 | } |
||
96 | if(!empty($info['namedFlags'])) { |
||
97 | foreach($info['namedFlags'] as $arg) echo " (--$arg)"; |
||
0 ignored issues
–
show
The expression
$info['namedFlags'] of type string|array<integer,string,{"0":"string"}> is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
98 | } |
||
99 | if(!empty($info['namedArgs'])) { |
||
100 | foreach($info['namedArgs'] as $arg) echo " --$arg=\"$arg value\""; |
||
0 ignored issues
–
show
The expression
$info['namedArgs'] of type string|array<integer,string,{"0":"string"}> is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
101 | } |
||
102 | echo "\n {$info['description']}\n\n"; |
||
103 | } |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Save an existing database and/or assets into an .sspak.phar file. |
||
108 | * Does the same as {@link save()} but doesn't require an existing site. |
||
109 | */ |
||
110 | function saveexisting($args) { |
||
111 | $executor = $this->executor; |
||
112 | |||
113 | $args->requireUnnamed(array('sspak file')); |
||
114 | $unnamedArgs = $args->getUnnamedArgs(); |
||
115 | $namedArgs = $args->getNamedArgs(); |
||
116 | |||
117 | $sspak = new SSPakFile($unnamedArgs[0], $executor); |
||
118 | |||
119 | // Look up which parts of the sspak are going to be saved |
||
120 | $pakParts = $args->pakParts(); |
||
121 | |||
122 | $filesystem = new FilesystemEntity(null, $executor); |
||
123 | |||
124 | if($pakParts['db']) { |
||
125 | $dbPath = escapeshellarg($namedArgs['db']); |
||
126 | $process = $filesystem->createProcess("cat $dbPath | gzip -c"); |
||
127 | $sspak->writeFileFromProcess('database.sql.gz', $process); |
||
128 | } |
||
129 | |||
130 | if($pakParts['assets']) { |
||
131 | $assetsParentArg = escapeshellarg(dirname($namedArgs['assets'])); |
||
132 | $assetsBaseArg = escapeshellarg(basename($namedArgs['assets'])); |
||
133 | $process = $filesystem->createProcess("cd $assetsParentArg && tar cfh - $assetsBaseArg | gzip -c"); |
||
134 | $sspak->writeFileFromProcess('assets.tar.gz', $process); |
||
135 | } |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Extracts an existing database and/or assets from a sspak into the given directory, |
||
140 | * defaulting the current working directory if the destination is not given. |
||
141 | */ |
||
142 | function extract($args) { |
||
143 | $executor = $this->executor; |
||
144 | |||
145 | $args->requireUnnamed(array('source sspak file')); |
||
146 | $unnamedArgs = $args->getUnnamedArgs(); |
||
147 | $file = $unnamedArgs[0]; |
||
148 | $dest = !empty($unnamedArgs[1]) ? $unnamedArgs[1] : getcwd(); |
||
149 | |||
150 | $sspak = new SSPakFile($file, $executor); |
||
151 | |||
152 | // Validation |
||
153 | if(!$sspak->exists()) throw new Exception("File '$file' doesn't exist."); |
||
154 | |||
155 | $phar = $sspak->getPhar(); |
||
156 | $phar->extractTo($dest); |
||
157 | } |
||
158 | |||
159 | function listTables($args) { |
||
0 ignored issues
–
show
|
|||
160 | $args->requireUnnamed(array('webroot')); |
||
161 | $unnamedArgs = $args->getUnnamedArgs(); |
||
162 | $webroot = $unnamedArgs[0]; |
||
163 | |||
164 | $db = new DatabaseConnector($webroot); |
||
165 | |||
166 | print_r($db->getTables()); |
||
167 | } |
||
168 | |||
169 | function saveCsv($args) { |
||
0 ignored issues
–
show
|
|||
170 | $args->requireUnnamed(array('webroot', 'path')); |
||
171 | $unnamedArgs = $args->getUnnamedArgs(); |
||
172 | $webroot = $unnamedArgs[0]; |
||
173 | $destPath = $unnamedArgs[1]; |
||
174 | |||
175 | if (!file_exists($destPath)) { |
||
176 | mkdir($destPath) || die("Can't create $destPath"); |
||
177 | } |
||
178 | if (!is_dir($destPath)) { |
||
179 | die("$destPath isn't a directory"); |
||
180 | } |
||
181 | |||
182 | $db = new DatabaseConnector($webroot); |
||
183 | |||
184 | foreach($db->getTables() as $table) { |
||
185 | $filename = $destPath . '/' . $table . '.csv'; |
||
186 | echo $filename . "...\n"; |
||
187 | touch($filename); |
||
188 | $writer = new CsvTableWriter($filename); |
||
189 | $db->saveTable($table, $writer); |
||
190 | } |
||
191 | echo "Done!"; |
||
192 | } |
||
193 | |||
194 | function loadCsv($args) { |
||
0 ignored issues
–
show
|
|||
195 | $args->requireUnnamed(array('input-path', 'webroot')); |
||
196 | $unnamedArgs = $args->getUnnamedArgs(); |
||
197 | |||
198 | $srcPath = $unnamedArgs[0]; |
||
199 | $webroot = $unnamedArgs[1]; |
||
200 | |||
201 | if (!is_dir($srcPath)) { |
||
202 | die("$srcPath isn't a directory"); |
||
203 | } |
||
204 | |||
205 | $db = new DatabaseConnector($webroot); |
||
206 | |||
207 | foreach($db->getTables() as $table) { |
||
208 | $filename = $srcPath . '/' . $table . '.csv'; |
||
209 | if(file_exists($filename)) { |
||
210 | echo $filename . "...\n"; |
||
211 | $reader = new CsvTableReader($filename); |
||
212 | $db->loadTable($table, $reader); |
||
213 | } else { |
||
214 | echo "$filename doesn't exist; skipping.\n"; |
||
215 | } |
||
216 | } |
||
217 | echo "Done!"; |
||
218 | } |
||
219 | /** |
||
220 | * Save a .sspak.phar file |
||
221 | */ |
||
222 | function save($args) { |
||
223 | $executor = $this->executor; |
||
224 | |||
225 | $args->requireUnnamed(array('source webroot', 'dest sspak file')); |
||
226 | |||
227 | $unnamedArgs = $args->getUnnamedArgs(); |
||
228 | $namedArgs = $args->getNamedArgs(); |
||
229 | |||
230 | $webroot = new Webroot($unnamedArgs[0], $executor); |
||
231 | $file = $unnamedArgs[1]; |
||
232 | if(file_exists($file)) throw new Exception( "File '$file' already exists."); |
||
233 | |||
234 | $sspak = new SSPakFile($file, $executor); |
||
235 | |||
236 | if(!empty($namedArgs['identity'])) { |
||
237 | // SSH private key |
||
238 | $webroot->setSSHItentityFile($namedArgs['identity']); |
||
239 | } |
||
240 | if(!empty($namedArgs['from-sudo'])) $webroot->setSudo($namedArgs['from-sudo']); |
||
241 | else if(!empty($namedArgs['sudo'])) $webroot->setSudo($namedArgs['sudo']); |
||
242 | |||
243 | // Look up which parts of the sspak are going to be saved |
||
244 | $pakParts = $args->pakParts(); |
||
245 | |||
246 | // Get the environment details |
||
247 | $details = $webroot->sniff(); |
||
248 | |||
249 | // Create a build folder for the sspak file |
||
250 | $buildFolder = "/tmp/sspak-" . rand(100000,999999); |
||
251 | $webroot->exec(array('mkdir', $buildFolder)); |
||
252 | |||
253 | $dbFile = "$buildFolder/database.sql.gz"; |
||
254 | $assetsFile = "$buildFolder/assets.tar.gz"; |
||
255 | $gitRemoteFile = "$buildFolder/git-remote"; |
||
256 | |||
257 | // Files to include in the .sspak.phar file |
||
258 | $fileList = array(); |
||
259 | |||
260 | // Save DB |
||
261 | if($pakParts['db']) { |
||
262 | // Check the database type |
||
263 | $dbFunction = 'getdb_'.$details['db_type']; |
||
264 | View Code Duplication | if(!method_exists($this,$dbFunction)) { |
|
265 | throw new Exception("Can't process database type '" . $details['db_type'] . "'"); |
||
266 | } |
||
267 | $this->$dbFunction($webroot, $details, $sspak, basename($dbFile)); |
||
268 | } |
||
269 | |||
270 | // Save Assets |
||
271 | if($pakParts['assets']) { |
||
272 | $this->getassets($webroot, $details['assets_path'], $sspak, basename($assetsFile)); |
||
273 | } |
||
274 | |||
275 | // Save git-remote |
||
276 | if($pakParts['git-remote']) { |
||
277 | $this->getgitremote($webroot, $sspak, basename($gitRemoteFile)); |
||
278 | } |
||
279 | |||
280 | // Remove the build folder |
||
281 | $webroot->unlink($buildFolder); |
||
282 | } |
||
283 | |||
284 | function getdb_MySQLPDODatabase($webroot, $conf, $sspak, $filename) { |
||
285 | return $this->getdb_MySQLDatabase($webroot, $conf, $sspak, $filename); |
||
286 | } |
||
287 | |||
288 | function getdb_MySQLDatabase($webroot, $conf, $sspak, $filename) { |
||
289 | $usernameArg = escapeshellarg("--user=".$conf['db_username']); |
||
290 | $passwordArg = escapeshellarg("--password=".$conf['db_password']); |
||
291 | $databaseArg = escapeshellarg($conf['db_database']); |
||
292 | |||
293 | $hostArg = ''; |
||
294 | $portArg = ''; |
||
295 | View Code Duplication | if (!empty($conf['db_server']) && $conf['db_server'] != 'localhost') { |
|
296 | if (strpos($conf['db_server'], ':')!==false) { |
||
297 | // Handle "server:port" format. |
||
298 | $server = explode(':', $conf['db_server'], 2); |
||
299 | $hostArg = escapeshellarg("--host=".$server[0]); |
||
300 | $portArg = escapeshellarg("--port=".$server[1]); |
||
301 | } else { |
||
302 | $hostArg = escapeshellarg("--host=".$conf['db_server']); |
||
303 | } |
||
304 | } |
||
305 | |||
306 | $filenameArg = escapeshellarg($filename); |
||
307 | |||
308 | $process = $webroot->createProcess("mysqldump --skip-opt --add-drop-table --extended-insert --create-options --quick --set-charset --default-character-set=utf8 $usernameArg $passwordArg $hostArg $portArg $databaseArg | gzip -c"); |
||
309 | $sspak->writeFileFromProcess($filename, $process); |
||
310 | return true; |
||
311 | } |
||
312 | |||
313 | function getdb_PostgreSQLDatabase($webroot, $conf, $sspak, $filename) { |
||
314 | $usernameArg = escapeshellarg("--username=".$conf['db_username']); |
||
315 | $passwordArg = "PGPASSWORD=".escapeshellarg($conf['db_password']); |
||
316 | $databaseArg = escapeshellarg($conf['db_database']); |
||
317 | $hostArg = escapeshellarg("--host=".$conf['db_server']); |
||
318 | $filenameArg = escapeshellarg($filename); |
||
319 | |||
320 | $process = $webroot->createProcess("$passwordArg pg_dump --clean --no-owner --no-tablespaces $usernameArg $hostArg $databaseArg | gzip -c"); |
||
321 | $sspak->writeFileFromProcess($filename, $process); |
||
322 | return true; |
||
323 | } |
||
324 | |||
325 | function getassets($webroot, $assetsPath, $sspak, $filename) { |
||
326 | $assetsParentArg = escapeshellarg(dirname($assetsPath)); |
||
327 | $assetsBaseArg = escapeshellarg(basename($assetsPath)); |
||
328 | |||
329 | $process = $webroot->createProcess("cd $assetsParentArg && tar cfh - $assetsBaseArg | gzip -c"); |
||
330 | $sspak->writeFileFromProcess($filename, $process); |
||
331 | } |
||
332 | |||
333 | function getgitremote($webroot, $sspak, $gitRemoteFile) { |
||
334 | // Only do anything if we're copying from a git checkout |
||
335 | $gitRepo = $webroot->getPath() .'/.git'; |
||
336 | if($webroot->exists($gitRepo)) { |
||
337 | // Identify current branch |
||
338 | $output = $webroot->exec(array('git', '--git-dir='.$gitRepo, 'branch')); |
||
339 | if(preg_match("/\* ([^ \n]*)/", $output['output'], $matches) && strpos("(no branch)", $matches[1])===false) { |
||
340 | // If there is a current branch, use that branch's remove |
||
341 | $currentBranch = trim($matches[1]); |
||
342 | $output = $webroot->exec(array('git', '--git-dir='.$gitRepo, 'config','--get',"branch.$currentBranch.remote")); |
||
343 | $remoteName = trim($output['output']); |
||
344 | if(!$remoteName) $remoteName = 'origin'; |
||
345 | |||
346 | // Default to origin |
||
347 | } else { |
||
348 | $currentBranch = null; |
||
349 | $remoteName = 'origin'; |
||
350 | } |
||
351 | |||
352 | // Determine the URL of that remote |
||
353 | $output = $webroot->exec(array('git', '--git-dir='.$gitRepo, 'config','--get',"remote.$remoteName.url")); |
||
354 | $remoteURL = trim($output['output']); |
||
355 | |||
356 | // Determine the current SHA |
||
357 | $output = $webroot->exec(array('git', '--git-dir='.$gitRepo, 'log','-1','--format=%H')); |
||
358 | $sha = trim($output['output']); |
||
359 | |||
360 | $content = "remote = $remoteURL\nbranch = $currentBranch\nsha = $sha\n"; |
||
361 | |||
362 | $sspak->writeFile($gitRemoteFile, $content); |
||
363 | |||
364 | return true; |
||
365 | } |
||
366 | return false; |
||
367 | } |
||
368 | |||
369 | /** |
||
370 | * Load an .sspak into an environment. |
||
371 | * Does not backup - be careful! */ |
||
372 | function load($args) { |
||
373 | $executor = $this->executor; |
||
374 | |||
375 | $args->requireUnnamed(array('source sspak file')); |
||
376 | |||
377 | // Set-up |
||
378 | $file = $args->unnamed(0); |
||
379 | $sspak = new SSPakFile($file, $executor); |
||
380 | $webroot = new Webroot(($args->unnamed(1) ?: '.'), $executor); |
||
381 | $webroot->setSudo($args->sudo('to')); |
||
382 | $pakParts = $args->pakParts(); |
||
383 | |||
384 | $namedArgs = $args->getNamedArgs(); |
||
385 | if(!empty($namedArgs['identity'])) { |
||
386 | // SSH private key |
||
387 | $webroot->setSSHItentityFile($namedArgs['identity']); |
||
388 | } |
||
389 | |||
390 | // Validation |
||
391 | if(!$sspak->exists()) throw new Exception( "File '$file' doesn't exist."); |
||
392 | |||
393 | // Push database, if necessary |
||
394 | $namedArgs = $args->getNamedArgs(); |
||
395 | if($pakParts['db'] && $sspak->contains('database.sql.gz')) { |
||
396 | $webroot->putdb($sspak, isset($namedArgs['drop-db'])); |
||
397 | } |
||
398 | |||
399 | // Push assets, if neccessary |
||
400 | if($pakParts['assets'] && $sspak->contains('assets.tar.gz')) { |
||
401 | $webroot->putassets($sspak); |
||
402 | } |
||
403 | } |
||
404 | |||
405 | /** |
||
406 | * Install an .sspak into a new environment. |
||
407 | */ |
||
408 | function install($args) { |
||
409 | $executor = $this->executor; |
||
410 | |||
411 | $args->requireUnnamed(array('source sspak file', 'dest new webroot')); |
||
412 | |||
413 | // Set-up |
||
414 | $file = $args->unnamed(0); |
||
415 | $webrootDir = $args->unnamed(1); |
||
416 | $sspak = new SSPakFile($file, $executor); |
||
417 | $webroot = new Webroot($webrootDir, $executor); |
||
418 | $webroot->setSudo($args->sudo('to')); |
||
419 | $pakParts = $args->pakParts(); |
||
420 | |||
421 | // Validation |
||
422 | if($webroot->exists($webroot->getPath())) throw new Exception( "Webroot '$webrootDir' already exists."); |
||
423 | if(!$sspak->exists()) throw new Exception( "File '$file' doesn't exist."); |
||
424 | |||
425 | // Create new dir |
||
426 | $webroot->exec(array('mkdir', $webroot->getPath())); |
||
427 | |||
428 | if($sspak->contains('git-remote')) { |
||
429 | $details = $sspak->gitRemoteDetails(); |
||
430 | $webroot->putgit($details); |
||
431 | } |
||
432 | |||
433 | // TODO: composer install needed. |
||
434 | |||
435 | // Push database, if necessary |
||
436 | $namedArgs = $args->getNamedArgs(); |
||
437 | if($pakParts['db'] && $sspak->contains('database.sql.gz')) { |
||
438 | $webroot->putdb($sspak, isset($namedArgs['drop-db'])); |
||
439 | } |
||
440 | |||
441 | // Push assets, if neccessary |
||
442 | if($pakParts['assets'] && $sspak->contains('assets.tar.gz')) { |
||
443 | $webroot->putassets($sspak); |
||
444 | } |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Bundle a .sspak into a self-extracting executable installer. |
||
449 | */ |
||
450 | function bundle($args) { |
||
451 | // TODO: throws require_once errors, fix before re-enabling. |
||
452 | |||
453 | $executor = $this->executor; |
||
454 | |||
455 | $args->requireUnnamed(array('source sspak file', 'dest executable file')); |
||
456 | |||
457 | // Set-up |
||
458 | $sourceFile = $args->unnamed(0); |
||
459 | $destFile = $args->unnamed(1); |
||
460 | |||
461 | $sspakScript = file_get_contents($_SERVER['argv'][0]); |
||
462 | // Broken up to not get detected by our sed command |
||
463 | $sspakScript .= "\n__halt_compiler();\n"."//"." TAR START?>\n"; |
||
464 | |||
465 | // Mark as self-extracting |
||
466 | $sspakScript = str_replace('$isSelfExtracting = false;', '$isSelfExtracting = true;', $sspakScript); |
||
467 | |||
468 | // Load the sniffer file |
||
469 | $snifferFile = dirname(__FILE__) . '/sspak-sniffer.php'; |
||
470 | $sspakScript = str_replace("\$snifferFileContent = '';\n", |
||
471 | "\$snifferFileContent = '" |
||
472 | . str_replace(array("\\","'"),array("\\\\", "\\'"), file_get_contents($snifferFile)) . "';\n", $sspakScript); |
||
473 | |||
474 | file_put_contents($destFile, $sspakScript); |
||
475 | chmod($destFile, 0775); |
||
476 | |||
477 | $executor->execLocal(array('cat', $sourceFile), array( |
||
478 | 'outputFile' => $destFile, |
||
479 | 'outputFileAppend' => true |
||
480 | )); |
||
481 | } |
||
482 | |||
483 | /** |
||
484 | * Transfer between environments without creating an sspak file |
||
485 | */ |
||
486 | function transfer($args) { |
||
487 | echo "Not implemented yet.\n"; |
||
488 | } |
||
489 | } |
||
490 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.