Completed
Push — 14.2 ( a89901...5b61af )
by Ralf
27:09
created

checkout-build-archives.php ➔ get_modules_per_repo()   D

Complexity

Conditions 18
Paths 160

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 25
nc 160
nop 0
dl 0
loc 43
rs 4.6734
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
#!/usr/bin/php -qC
2
<?php
3
/**
4
 * EGroupware - checkout, build and release archives, build Debian and rpm packages
5
 *
6
 * @link http://www.egroupware.org
7
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
8
 * @author [email protected]
9
 * @copyright (c) 2009-16 by Ralf Becker <[email protected]>
10
 * @version $Id$
11
 */
12
13
if (php_sapi_name() !== 'cli')	// security precaution: forbit calling setup-cli as web-page
14
{
15
	die('<h1>checkout-build-archives.php must NOT be called as web-page --> exiting !!!</h1>');
16
}
17
date_default_timezone_set('Europe/Berlin');	// to get ride of 5.3 warnings
18
19
$verbose = 0;
20
$config = array(
21
	'packagename' => 'egroupware-epl',
22
	'version' => '14.3',        // '14.3'
23
	'packaging' => date('Ymd'), // '20160520'
24
	'branch'  => '14.2',        // checked out branch
25
	'tag' => '$version.$packaging',	// name of tag
26
	'checkoutdir' => realpath(__DIR__.'/../..'),
27
	'egw_buildroot' => '/tmp/build_root/epl_14.2_buildroot',
28
	'sourcedir' => '/home/download/stylite-epl/egroupware-epl-14.2',
29
	/* svn-config currently not used, as we use .mrconfig to define modules and urls
30
	'svntag' => 'tags/$version.$packaging',
31
	'svnbase' => 'svn+ssh://[email protected]/egroupware',
32
	'stylitebase' => 'svn+ssh://[email protected]/stylite',
33
	'svnbranch' => 'branches/14.2',         //'trunk', // 'branches/1.6' or 'tags/1.6.001'
34
	'svnalias' => 'aliases/default-ssh',    // default alias
35
	'extra' => array('$stylitebase/$svnbranch/stylite', '$stylitebase/$svnbranch/esyncpro', '$stylitebase/trunk/archive'),//, '$stylitebase/$svnbranch/groups'), //,'svn+ssh://[email protected]/stylite/trunk/eventmgr'),
36
	*/
37
	'extra' => array('stylite', 'esyncpro', 'archive'),	// create an extra archive for given apps
38
		// these apps are placed in egroupware-epl-contrib archive
39
		//'contrib' => array('phpgwapi', 'etemplate', 'jdots', 'phpbrain', 'wiki', 'sambaadmin', 'sitemgr', 'phpfreechat')),
40
	'aliasdir' => 'egroupware',             // directory created by the alias
41
	'types' => array('tar.bz2','tar.gz','zip','all.tar.bz2'),
42
	// add given extra-apps or (uncompressed!) archives to above all.tar.bz2 archive
43
	'all-add' => array(/*'contrib',*/ '/home/stylite/epl-trunk/phpfreechat_data_public.tar'),
44
	// diverse binaries we need
45
	'svn' => trim(`which svn`),
46
	'tar' => trim(`which tar`),
47
	'mv' => trim(`which mv`),
48
	'rm' => trim(`which rm`),
49
	'zip' => trim(`which zip`),
50
	'bzip2' => trim(`which bzip2`),
51
	'clamscan' => trim(`which clamscan`),
52
	'freshclam' => trim(`which freshclam`),
53
	'git' => trim(`which git`),
54
	'mr'  => trim(`which mr`),
55
	'gpg' => trim(`which gpg`),
56
	'editor' => trim(`which vi`),
57
	'rsync' => trim(`which rsync`).' --progress -e ssh --exclude "*-stylite-*" --exclude "*-esyncpro-*"',
58
	'composer' => ($composer=trim(`which composer.phar`)) ? $composer.' install --ignore-platform-reqs' : '',
59
	'after-checkout' => 'rm -rf */source */templates/*/source pixelegg/content-element-library',
60
	'packager' => '[email protected]',
61
	'obs' => '/home/stylite/obs/stylite-epl',
62
	'obs_package_alias' => '',	// name used in obs package, if different from packagename
63
	'changelog' => false,   // eg. '* 1. Zeile\n* 2. Zeile' for debian.changes
64
	'changelog_packager' => 'Ralf Becker <[email protected]>',
65
	'editchangelog' => '* ',
66
	//'sfuser' => 'ralfbecker',
67
	//'release' => '$sfuser,[email protected]:/home/frs/project/e/eg/egroupware/eGroupware-$version/eGroupware-$version.$packaging/',
68
	'copychangelog' => '$sourcedir/README', //'$sfuser,[email protected]:/home/frs/project/e/eg/egroupware/README',
69
	'skip' => array(),
70
	'run' => array('checkout','editchangelog','tag','copy','virusscan','create','sign','obs','copychangelog'),
71
	'patchCmd' => '# run cmd after copy eg. "cd $egw_buildroot; patch -p1 /path/to/patch"',
72
	'github_user' => 'ralfbecker',	// Github user for following token
73
	'github_token' => '',	// Github repo personal access token from above user
74
);
75
76
// process config from command line
77
$argv = $_SERVER['argv'];
78
$prog = array_shift($argv);
79
80
while(($arg = array_shift($argv)))
81
{
82
	if ($arg == '-v' || $arg == '--verbose')
83
	{
84
		++$verbose;
85
	}
86
	elseif($arg == '-h' || $arg == '--help')
87
	{
88
		usage();
89
	}
90
	elseif(substr($arg,0,2) == '--' && isset($config[$name=substr($arg,2)]))
91
	{
92
		$value = array_shift($argv);
93
		switch($name)
94
		{
95
			case 'extra':	// stored as array and allow to add/delete items with +/- prefix
96
			case 'types':
97
			case 'skip':
98
			case 'run':
99
				if ($value[0] == '+')
100
				{
101
					$config[$name] = array_unique(array_merge($config[$name],preg_split('/[ ,]+/',substr($value,1))));
102
				}
103
				elseif ($value[0] == '-')
104
				{
105
					$config[$name] = array_diff($config[$name],preg_split('/[ ,]+/',substr($value,1)));
106
				}
107
				else
108
				{
109
					$config[$name] = array_unique(preg_split('/[ ,]+/',$value));
110
				}
111
				break;
112
113
			case 'svntag':
114
			case 'tag':
115
			case 'release':
116
			case 'copychangelog':
117
				$config[$name] = $value;
118
				if ($value) array_unshift($config['run'],$name);
119
				break;
120
121
			case 'editchangelog':
122
				$config[$name] = $value ? $value : true;
123
				if (!in_array('editchangelog',$config['run']))
124
				{
125
					array_unshift($config['run'],'editchangelog');
126
				}
127
				break;
128
129
			case 'obs':
130
				if (!is_dir($value))
131
				{
132
					usage("Path '$value' not found!");
133
				}
134
				if (!in_array('obs',$config['run'])) $config['run'][] = 'obs';
135
				// fall through
136
			default:
137
				$config[$name] = $value;
138
				break;
139
		}
140
	}
141
	else
142
	{
143
		usage("Unknown argument '$arg'!");
144
	}
145
}
146
if ($verbose > 1)
147
{
148
	echo "Using following config:\n";
149
	print_r($config);
150
}
151
$svn = $config['svn'];
152
153
foreach(array_diff($config['run'],$config['skip']) as $func)
154
{
155
	chdir(dirname(__FILE__));	// make relative filenames work, if other command changes dir
156
	call_user_func('do_'.$func);
157
}
158
159
/**
160
 * Read changelog for given branch from (last) tag or given revision from svn
161
 *
162
 * @param string $_path relativ path to repo starting with $config['aliasdir']
163
 * @param string $log_pattern =null	a preg regular expression or start of line a log message must match, to be returned
164
 * 	if regular perl regular expression given only first expression in brackets \\1 is used,
165
 * 	for a start of line match, only the first line is used, otherwise whole message is used
166
 * @param string& $last_tag =null from which tag on to query logs
167
 * @param string $prefix ='* ' prefix, which if not presend should be added to all log messages
168
 * @return string with changelog
169
 */
170
function get_changelog_from_git($_path, $log_pattern=null, &$last_tag=null, $prefix='* ')
171
{
172
	//echo __FUNCTION__."('$branch_url','$log_pattern','$revision','$prefix')\n";
173
	global $config;
174
175
	$path = str_replace($config['aliasdir'], $config['checkoutdir'], $_path);
176
	if (!file_exists($path) || !is_dir($path) || !file_exists($path.'/.git'))
177
	{
178
		throw new Exception("$path is not a git repository!");
179
	}
180
	if (empty($last_tag))
181
	{
182
		$last_tag = get_last_git_tag();
183
	}
184
185
	$cmd = $config['git'].' log '.escapeshellarg($last_tag.'..HEAD');
186
	if (getcwd() != $path) $cmd = 'cd '.$path.'; '.$cmd;
187
	$output = null;
188
	run_cmd($cmd, $output);
189
190
	$changelog = '';
191
	foreach($output as $line)
0 ignored issues
show
Bug introduced by
The expression $output of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. 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:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
192
	{
193
		if (substr($line, 0, 4) == "    " && ($msg = _match_log_pattern(substr($line, 4), $log_pattern, $prefix)))
194
		{
195
			$changelog .= $msg."\n";
196
		}
197
	}
198
	return $changelog;
199
}
200
201
/**
202
 * Get module path (starting with $config['aliasdir']) per repo from .mrconfig for svn and git
203
 *
204
 * @return array with $repro_url => $path => $url, eg. array(
205
 *		"[email protected]:EGroupware/egroupware.git" => array(
206
 *			"egroupware" => "[email protected]:EGroupware/egroupware.git"),
207
 *		"[email protected]:EGroupware/tracker.git" => array(
208
 *			"egroupware/tracker" => "[email protected]:EGroupware/tracker.git"),
209
 *		"svn+ssh://[email protected]/stylite" => array(
210
 *			"egroupware/stylite] => svn+ssh://[email protected]/stylite/branches/14.2/stylite",
211
 *			"egroupware/esyncpro] => svn+ssh://[email protected]/stylite/branches/14.2/esyncpro",
212
 */
213
function get_modules_per_repo()
214
{
215
	global $config, $verbose;
216
217
	if ($verbose) echo "Get modules from .mrconfig in checkoutdir $config[checkoutdir]\n";
218
219
	if (!is_dir($config['checkoutdir']))
220
	{
221
		throw new Exception("checkout directory '{$config['checkoutdir']} does NOT exists or is NO directory!");
222
	}
223
	if (!($mrconfig = file_get_contents($path=$config['checkoutdir'].'/.mrconfig')))
224
	{
225
		throw new Exception("$path not found!");
226
	}
227
	$module = $baseurl = null;
228
	$modules = array();
229
	foreach(explode("\n", $mrconfig) as $line)
230
	{
231
		$matches = null;
232
		if (isset($baseurl))
233
		{
234
			$line = str_replace("\$(git config --get remote.origin.url|sed 's|/egroupware.git||')", $baseurl, $line);
235
		}
236
		if ($line && $line[0] == '[' && preg_match('/^\[([^]]*)\]/', $line, $matches))
237
		{
238
			if (in_array($matches[1], array('DEFAULT', 'api/js/ckeditor', 'api/src/Accounts/Ads', 'phpgwapi/js/ckeditor', 'phpgwapi/inc/adldap')))
239
			{
240
				$module = null;
241
				continue;
242
			}
243
			$module = (string)$matches[1];
244
		}
245
		elseif (isset($module) && preg_match('/^checkout\s*=\s*(git\s+clone\s+(-b\s+[0-9.]+\s+)?((git|http)[^ ]+)|svn\s+checkout\s+((svn|http)[^ ]+))/', $line, $matches))
246
		{
247
			$repo = $url = substr($matches[1], 0, 3) == 'svn' ? $matches[5] : $matches[3];
248
			if (substr($matches[1], 0, 3) == 'svn') $repo = preg_replace('#/(trunk|branches)/.*$#', '', $repo);
249
			$modules[$repo][$config['aliasdir'].($module ? '/'.$module : '')] = $url;
250
			if ($module === '' && !isset($baseurl)) $baseurl = str_replace('/egroupware.git', '', $url);
251
		}
252
	}
253
	if ($verbose) print_r($modules);
254
	return $modules;
255
}
256
257
/**
258
 * Get commit of last git tag matching a given pattern
259
 *
260
 * @return string name of last tag matching $config['version'].'.*'
261
 */
262
function get_last_git_tag()
263
{
264
	global $config;
265
266
	if (!is_dir($config['checkoutdir']))
267
	{
268
		throw new Exception("checkout directory '{$config['checkoutdir']} does NOT exists or is NO directory!");
269
	}
270
	chdir($config['checkoutdir']);
271
272
	$cmd = $config['git'].' tag -l '.escapeshellarg($config['version'].'.*');
273
	$output = null;
274
	run_cmd($cmd, $output);
275
276
	return trim(array_pop($output));
277
}
278
279
/**
280
 * Checkout or update EGroupware
281
 *
282
 * Ensures an existing checkout is from the correct branch! Otherwise it get's deleted
283
 */
284
function do_checkout()
285
{
286
	global $config;
287
288
	echo "Starting checkout/update\n";
289
	if (!file_exists($config['checkoutdir']))
290
	{
291
		$cmd = $config['git'].' clone '.(!empty($config['branch']) ? ' -b '.$config['branch'] : '').
292
			' [email protected]:EGroupware/egroupware.git '.$config['checkoutdir'];
293
		run_cmd($cmd);
294
		run_cmd('mr up');	// need to run mr up twice for new checkout, because chained .mrconfig wont run first time (because not there yet!)
295
	}
296 View Code Duplication
	elseif (!is_dir($config['checkoutdir']) || !is_writable($config['checkoutdir']))
297
	{
298
		throw new Exception("svn checkout directory '{$config['checkoutdir']} exists and is NO directory or NOT writable!");
299
	}
300
	chdir($config['checkoutdir']);
301
302
	run_cmd('mr up');
303
}
304
305
/**
306
 * Create a tag using mr in svn or git for current checked out branch
307
 */
308
function do_tag()
309
{
310
	global $config;
311
312
	if (!is_dir($config['checkoutdir']))
313
	{
314
		throw new Exception("checkout directory '{$config['checkoutdir']} does NOT exists or is NO directory!");
315
	}
316
	chdir($config['checkoutdir']);
317
318
	$config['tag'] = config_translate('tag');	// allow to use config vars like $version in tag
319
320
	if (empty($config['tag'])) return;	// otherwise we copy everything in svn root!
321
322
	echo "Creating tag $config[tag]\n";
323
324
	$cmd = $config['mr'].' tag '.escapeshellarg($config['tag']).' '.escapeshellarg('Creating '.$config['tag']);
325
	run_cmd($cmd);
326
}
327
328
/**
329
 * Release sources by rsync'ing them to a distribution / download directory
330
 */
331
function do_release()
332
{
333
	global $config,$verbose;
334
335
	// push local changes to Github incl. tags
336
	if ($verbose) echo "Pushing changes and tags\n";
337
	chdir($config['checkoutdir']);
338
	run_cmd($config['mr']. ' up');		// in case someone else pushed something
339
	chdir($config['checkoutdir']);
340
	run_cmd($config['git'].' push');	// regular commits like changelog
341
	$tag = config_translate('tag');
342
	run_cmd($config['mr']. ' push origin '.$tag);	// pushing tags in all apps
343
344
	if (empty($config['github_user']) || empty($config['github_token']))
345
	{
346
		throw new Exception("No personal Github user or access token specified (--github_token)!");
347
	}
348
	if (empty($config['changelog']))
349
	{
350
		$config['changelog'] = parse_current_changelog();
351
	}
352
	$data = array(
353
		'tag_name' => $tag,
354
		'name' => $tag,
355
		'target_commitish' => $config['branch'],
356
		'body' => $config['changelog'],
357
	);
358
	$response = github_api("/repos/EGroupware/egroupware/releases", $data);
359
	$upload_url = preg_replace('/{\?[^}]+}$/', '', $response['upload_url']);	// remove {?name,label} template
360
361
	$archives = $config['sourcedir'].'/*egroupware-epl-'.$config['version'].'.'.$config['packaging'].'*';
362
363
	foreach(glob($archives) as $path)
364
	{
365
		$label = null;
366
		if (substr($path, -4) == '.zip')
367
		{
368
			$content_type = 'application/zip';
369
		}
370
		elseif(substr($path, -7) == '.tar.gz')
371
		{
372
			$content_type = 'application/x-gzip';
373
		}
374
		elseif(substr($path, -8) == '.tar.bz2')
375
		{
376
			$content_type = 'application/x-bzip2';
377
		}
378
		elseif(substr($path, -8) == '.txt.asc')
379
		{
380
			$content_type = 'text/plain';
381
			$label = 'Signed hashes of downloads';
382
		}
383
		else
384
		{
385
			continue;
386
		}
387
		$name = basename($path);
388
		github_api($upload_url, array(
389
			'name' => $name,
390
			'label' => isset($label) ? $label : $name,
391
		), 'FILE', $path, $content_type);
392
	}
393
394
	if (!empty($config['release']))
395
	{
396
		$target = config_translate('release');	// allow to use config vars like $svnbranch in module
397
		$cmd = $config['rsync'].' '.$archives.' '.$target;
398
		if ($verbose) echo $cmd."\n";
399
		passthru($cmd);
400
	}
401
}
402
403
/**
404
 * Sending a Github API request
405
 *
406
 * @param string $_url url of just path where to send request to (https://api.github.com is added automatic)
407
 * @param string|array $data payload, array get automatic added as get-parameter or json_encoded for POST
408
 * @param string $method ='POST'
409
 * @param string $upload =null path of file to upload, payload for request with $method='FILE'
410
 * @param string $content_type =null
411
 * @throws Exception
412
 * @return array with response
413
 */
414
function github_api($_url, $data, $method='POST', $upload=null, $content_type=null)
415
{
416
	global $config, $verbose;
417
418
	$url = $_url[0] == '/' ? 'https://api.github.com'.$_url : $_url;
419
	$c = curl_init();
420
	curl_setopt($c, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
421
	curl_setopt($c, CURLOPT_USERPWD, $config['github_user'].':'.$config['github_token']);
422
	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
423
	curl_setopt($c, CURLOPT_USERAGENT, basename(__FILE__));
424
	curl_setopt($c, CURLOPT_TIMEOUT, 240);
425
	curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
426
427
	switch($method)
428
	{
429
		case 'POST':
430
			curl_setopt($c, CURLOPT_POST, true);
431
			if (is_array($data)) $data = json_encode($data, JSON_FORCE_OBJECT);
432
			curl_setopt($c, CURLOPT_POSTFIELDS, $data);
433
			break;
434
		case 'GET':
435
			if(count($data)) $url .= '?' . http_build_query($data);
436
			break;
437
		case 'FILE':
438
			curl_setopt($c, CURLOPT_HTTPHEADER, array("Content-type: $content_type"));
439
			curl_setopt($c, CURLOPT_POST, true);
440
			curl_setopt($c, CURLOPT_POSTFIELDS, file_get_contents($upload));
441
			if(count($data)) $url .= '?' . http_build_query($data);
442
			break;
443
		default:
444
			throw new Exception(__FUNCTION__.": Unknown/unimplemented method=$method!");
445
	}
446
	curl_setopt($c, CURLOPT_URL, $url);
447
448
	if (is_string($data)) $short_data = strlen($data) > 100 ? substr($data, 0, 100).' ...' : $data;
449
	if ($verbose) echo "Sending $method request to $url ".(isset($short_data)&&$method!='GET'?$short_data:'')."\n";
450
451
	if (($response = curl_exec($c)) === false)
452
	{
453
		// run failed request again to display response including headers
454
		curl_setopt($c, CURLOPT_HEADER, true);
455
		curl_setopt($c, CURLOPT_RETURNTRANSFER, false);
456
		curl_exec($c);
457
		throw new Exception("$method request to $url failed ".(isset($short_data)&&$method!='GET'?$short_data:''));
458
	}
459
460
	if ($verbose) echo (strlen($response) > 200 ? substr($response, 0, 200).' ...' : $response)."\n";
461
462
	curl_close($c);
463
464
	return json_decode($response, true);
465
}
466
467
/**
468
 * Fetch a config value allowing to use config vars like $svnbranch in it
469
 *
470
 * @param string $name
471
 * @param string $value =null value to use, default $config[$name]
472
 */
473
function config_translate($name, $value=null)
474
{
475
	global $config;
476
477
	if (!isset($value)) $value = $config[$name];
478
	if (is_string($value) && strpos($value, '$') !== false)
479
	{
480
		$translate = array();
481
		foreach($config as $n => $val)
482
		{
483
			if (is_string($val)) $translate['$'.$n] = $val;
484
		}
485
		$value = strtr($value, $translate);
486
	}
487
	return $value;
488
}
489
490
/**
491
 * Copy changelog by rsync'ing it to a distribution / download directory
492
 */
493
function do_copychangelog()
494
{
495
	global $config;
496
497
	$changelog = __DIR__.'/debian.changes';
498
	$cmd = $config['rsync'].' '.$changelog.' '.config_translate('copychangelog');
499
	passthru($cmd);
500
}
501
502
/**
503
 * Query changelog and let user edit it
504
 */
505
function do_editchangelog()
506
{
507
	global $config,$svn;
508
509
	echo "Querying changelog from Git/SVN\n";
510
	if (!isset($config['modules']))
511
	{
512
		$config['modules'] = get_modules_per_repo();
513
	}
514
	// query changelog per repo
515
	$changelog = '';
516
	$last_tag = null;
517
	foreach($config['modules'] as $branch_url => $modules)
518
	{
519
		$revision = null;
520
		if (substr($branch_url, -4) == '.git')
521
		{
522
			list($path) = each($modules);
523
			$changelog .= get_changelog_from_git($path, $config['editchangelog'], $last_tag);
524
		}
525
		else
526
		{
527
			$changelog .= get_changelog_from_svn($branch_url, $config['editchangelog'], $revision);
528
		}
529
	}
530
	$logfile = tempnam('/tmp','checkout-build-archives');
531
	file_put_contents($logfile,$changelog);
532
	$cmd = $config['editor'].' '.escapeshellarg($logfile);
533
	passthru($cmd);
534
	$config['changelog'] = file_get_contents($logfile);
535
	// remove trailing newlines
536
	while (substr($config['changelog'],-1) == "\n")
537
	{
538
		$config['changelog'] = substr($config['changelog'],0,-1);
539
	}
540
	// allow user to abort, by deleting the changelog
541
	if (strlen($config['changelog']) <= 2)
542
	{
543
		die("\nChangelog must not be empty --> aborting\n\n");
544
	}
545
	// commit changelog
546
	$changelog = $config['checkoutdir'].'/doc/rpm-build/debian.changes';
547
	if (!file_exists($changelog))
548
	{
549
		throw new Exception("Changelog '$changelog' not found!");
550
	}
551
	file_put_contents($changelog, update_changelog(file_get_contents($changelog)));
552
	if (file_exists($config['checkoutdir'].'/.git'))
553
	{
554
		$cmd = $config['git']." commit -m 'Changelog for $config[version].$config[packaging]' ".$changelog;
555
	}
556
	else
557
	{
558
		$cmd = $svn." commit -m 'Changelog for $config[version].$config[packaging]' ".$changelog;
559
	}
560
	run_cmd($cmd);
561
562
	// update obs changelogs (so all changlogs are updated in case of a later error and changelog step can be skiped)
563
	do_obs(true);	// true: only update debian.changes in obs checkouts
564
}
565
566
/**
567
 * Read changelog for given branch from (last) tag or given revision from svn
568
 *
569
 * @param string $branch_url ='svn+ssh://[email protected]/egroupware/branches/Stylite-EPL-10.1'
570
 * @param string $log_pattern =null	a preg regular expression or start of line a log message must match, to be returned
571
 * 	if regular perl regular expression given only first expression in brackets \\1 is used,
572
 * 	for a start of line match, only the first line is used, otherwise whole message is used
573
 * @param string& $revision =null from which to HEAD the log should be retrieved, default search revision of latest tag in ^/tags
574
 * @param string $prefix ='* ' prefix, which if not presend should be added to all log messages
575
 * @return string with changelog
576
 */
577
function get_changelog_from_svn($branch_url, $log_pattern=null, &$revision=null, $prefix='* ')
578
{
579
	//echo __FUNCTION__."('$branch_url','$log_pattern','$revision','$prefix')\n";
580
	global $config,$verbose,$svn;
581
582
	if (is_null($revision))
583
	{
584
		list($tags_url,$branch) = preg_split('#/(branches/|trunk)#',$branch_url);
585
		if (empty($branch)) $branch = $config['version'];
586
		$tags_url .= '/tags';
587
		$pattern='|/tags/('.preg_quote($config['version'], '|').'\.[0-9.]+)|';
588
		$matches = null;
589
		$revision = get_last_svn_tag($tags_url,$pattern,$matches);
590
		$tag = $matches[1];
591
	}
592
	elseif(!is_numeric($revision))
593
	{
594
		$revision = get_last_svn_tag($tags_url,$tag=$revision);
0 ignored issues
show
Bug introduced by
The variable $tags_url seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
595
	}
596
	$cmd = $svn.' log --xml -r '.escapeshellarg($revision.':HEAD').' '.escapeshellarg($branch_url);
597
	if (($v = $verbose))
598
	{
599
		echo "Querying SVN for log from r$revision".($tag ? " ($tag)" : '').":\n$cmd\n";
600
		$verbose = false;	// otherwise no $output!
601
	}
602
	$output = array();
603
	run_cmd($cmd,$output);
604
	$verbose = $v;
605
	array_shift($output);	// remove the command
606
607
	$xml = simplexml_load_string($output=implode("\n",$output));
608
	$message = '';
609
	foreach($xml as $log)
610
	{
611
		if (!($msg = _match_log_pattern($log->msg, $log_pattern, $prefix))) continue;	// no match --> ignore
612
613
		$message .= $msg."\n";
614
	}
615
	if ($verbose) echo $message;
616
617
	return $message;
618
}
619
620
/**
621
 * Return first row of matching log lines always prefixed with $prefix
622
 *
623
 * @param string $msg whole log message
624
 * @param string $log_pattern
625
 * @param string $prefix ='* '
626
 * @return string
627
 */
628
function _match_log_pattern($msg, $log_pattern, $prefix='* ')
629
{
630
	$pattern_len = strlen($log_pattern);
631
	$prefix_len = strlen($prefix);
632
633
	$matches = null;
634
	if ($log_pattern[0] == '/' && preg_match($log_pattern,$msg,$matches))
635
	{
636
		$msg = $matches[1];
637
	}
638
	elseif($log_pattern && $log_pattern[0] != '/' && substr($msg,0,$pattern_len) == $log_pattern)
639
	{
640
		list($msg) = explode("\n",$msg);
641
	}
642
	elseif($log_pattern)
643
	{
644
		return null;
645
	}
646
	if ($prefix_len && substr($msg,0,$prefix_len) != $prefix) $msg = $prefix.$msg;
647
648
	return $msg;
649
}
650
651
/**
652
 * Get revision of last svn tag matching a given pattern in the log message
653
 *
654
 * @param string $tags_url
655
 * @param string $pattern which has to be contained in the log message (NOT the tag itself)
656
 * 	or (perl) regular expression against which log message is matched
657
 * @param array &$matches=null on return matches of preg_match
658
 * @return int revision of last svn tag matching pattern
659
 */
660
function get_last_svn_tag($tags_url,$pattern,&$matches=null)
661
{
662
	global $verbose,$svn;
663
664
	$cmd = $svn.' log --xml --limit 40 -v '.escapeshellarg($tags_url);
665
	if (($v = $verbose))
666
	{
667
		echo "Querying SVN for last tags\n$cmd\n";
668
		$verbose = false;	// otherwise no $output!
669
	}
670
	$output = array();
671
	run_cmd($cmd,$output);
672
	$verbose = $v;
673
	array_shift($output);	// remove the command
674
675
	$xml = simplexml_load_string($output=implode("\n",$output));
676
	$is_regexp = $pattern[0] == substr($pattern, -1);
677
	foreach($xml as $log)
678
	{
679
		//print_r($log);
680
		if (!$is_regexp && strpos($log->paths->path, $pattern) !== false ||
681
			$is_regexp && preg_match($pattern, $log->paths->path, $matches))
682
		{
683
			if ($verbose) echo "Revision {$log['revision']} matches".($matches?': '.$matches[1] : '')."\n";
684
			return (int)$log['revision'];
685
		}
686
	}
687
	return null;
688
}
689
690
/**
691
 * Copy archive files to obs checkout and commit them
692
 *
693
 * @param boolean $only_update_changelog =false true update debian.changes, but nothing else, nor commit it
694
 */
695
function do_obs($only_update_changelog=false)
696
{
697
	global $config,$verbose;
698
699
	if (!is_dir($config['obs']))
700
	{
701
		usage("Path '$config[obs]' not found!");
702
	}
703
	if ($verbose) echo $only_update_changelog ? "Updating OBS changelogs\n" : "Updating OBS checkout\n";
704
	run_cmd('osc up '.$config['obs']);
705
706
	$n = 0;
707
	foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config['obs'])) as $path)
708
	{
709
		if (basename(dirname($path)) == '.osc' ||
710
			!preg_match('/\/('.preg_quote($config['packagename']).
711
				($config['obs_package_alias'] ? '|'.preg_quote($config['obs_package_alias']) : '').
712
				')[a-z-]*-('.preg_quote($config['version']).'|14.2|trunk)/',$path))
713
		{
714
			continue;
715
		}
716
		$matches = null;
717
		if (preg_match('/\/('.preg_quote($config['packagename']).'[a-z-]*)-'.preg_quote($config['version']).'\.[0-9.]+[0-9](\.tar\.(gz|bz2))$/',$path,$matches) &&
718
			file_exists($new_name=$config['sourcedir'].'/'.$matches[1].'-'.$config['version'].'.'.$config['packaging'].$matches[2]))
719
		{
720
			if (basename($path) != basename($new_name))
721
			{
722
				unlink($path);
723
				if ($verbose) echo "rm $path\n";
724
			}
725
			copy($new_name,dirname($path).'/'.basename($new_name));
726
			if ($verbose) echo "cp $new_name ".dirname($path)."/\n";
727
			++$n;
728
		}
729
		// if we have no changelog (eg. because commands run separate), try parsing it from changelog file
730
		if (empty($config['changelog']))
731
		{
732
			$config['changelog'] = parse_current_changelog();
733
		}
734
		// updating dsc, spec and changelog files
735
		if (!$only_update_changelog && (substr($path,-4) == '.dsc' || substr($path,-5) == '.spec') ||
736
			!empty($config['changelog']) && basename($path) == 'debian.changes')
737
		{
738
			$content = $content_was = file_get_contents($path);
739
740
			if (substr($path,-4) == '.dsc' || substr($path,-5) == '.spec')
741
			{
742
				$content = preg_replace('/^Version: '.preg_quote($config['version']).'\.[0-9.]+[0-9]/m','Version: '.$config['version'].'.'.$config['packaging'],$content);
743
			}
744
			if (substr($path,-4) == '.dsc')
745
			{
746
				$content = preg_replace('/^(Debtransform-Tar: '.preg_quote($config['packagename']).'[a-z-]*)-'.
747
					preg_quote($config['version']).'\.[0-9.]+[0-9](\.tar\.(gz|bz2))$/m',
748
					'\\1-'.$config['version'].'.'.$config['packaging'].'\\2',$content);
749
			}
750
			if (basename($path) == 'debian.changes' && strpos($content,$config['version'].'.'.$config['packaging']) === false)
751
			{
752
				$content = update_changelog($content);
753
			}
754
			if (!empty($config['changelog']) && substr($path,-5) == '.spec' &&
755
				($pos_changelog = strpos($content,'%changelog')) !== false)
756
			{
757
				$pos_changelog += strlen("%changelog\n");
758
				$content = substr($content,0,$pos_changelog).' *'.date('D M d Y').' '.$config['changelog_packager']."\n".
759
					$config['changelog']."\n".substr($content,$pos_changelog);
760
			}
761
			if ($content != $content_was)
762
			{
763
				file_put_contents($path,$content);
764
				if ($verbose) echo "Updated $path\n";
765
				++$n;
766
			}
767
		}
768
	}
769
	if ($n && !$only_update_changelog)
770
	{
771
		echo "$n files updated in OBS checkout ($config[obs]), commiting them now...\n";
772
		//run_cmd('osc status '.$config['obs']);
773
		run_cmd('osc addremove '.$config['obs'].'/*');
774
		run_cmd('osc commit -m '.escapeshellarg('Version: '.$config['version'].'.'.$config['packaging'].":\n".$config['changelog']).' '.$config['obs']);
775
	}
776
}
777
778
/**
779
 * Parse current changelog from debian.changes file
780
 *
781
 * @return string changelog entries without header and footer lines
782
 */
783
function parse_current_changelog()
784
{
785
	global $config;
786
787
	$changelog = file_get_contents($config['checkoutdir'].'/doc/rpm-build/debian.changes');
788
	$lines = explode("\n", $changelog, 100);
789
	foreach($lines as $n => $line)
790
	{
791
		if (preg_match($preg='/^'.preg_quote($config['packagename']).' \('.preg_quote($config['version'].'.'.$config['packaging']).'/', $line))
792
		{
793
			$n += empty($lines[$n+1]) ? 2 : 1;	// overead empty line behind header
794
			$logentry = '';
795
			while($lines[$n])	// entry is terminated by empty line
796
			{
797
				$logentry .= (substr($lines[$n], 0, 2) == '  ' ? substr($lines[$n], 2) : $lines[$n])."\n";
798
				++$n;
799
			}
800
			return substr($logentry, 0, -1);	// remove training "\n"
801
		}
802
	}
803
	return null;	// paragraph for current version NOT found
804
}
805
806
/**
807
 * Update content of debian changelog file with new content from $config[changelog]
808
 *
809
 * @param string $content existing changelog content
810
 * @return string updated changelog content
811
 */
812
function update_changelog($content)
813
{
814
	global $config;
815
816
	list($header) = explode("\n", $content);
817
	$new_header = preg_replace('/\('.preg_quote($config['version']).'\.[0-9.]+[0-9](.*)\)/','('.$config['version'].'.'.$config['packaging'].'\\1)', $header);
818
	if (substr($config['changelog'],0,2) != '  ') $config['changelog'] = '  '.implode("\n  ",explode("\n",$config['changelog']));
819
	$content = $new_header."\n\n".$config['changelog'].
820
		"\n\n -- ".$config['changelog_packager'].'  '.date('r')."\n\n".$content;
821
822
	return $content;
823
}
824
825
/**
826
 * Sign sha1sum file
827
 */
828
function do_sign()
829
{
830
	global $config;
831
832 View Code Duplication
	if (substr($config['sourcedir'],0,2) == '~/')	// sha1_file cant deal with '~/rpm'
833
	{
834
		$config['sourcedir'] = getenv('HOME').substr($config['sourcedir'],1);
835
	}
836
	$sumsfile = $config['sourcedir'].'/sha1sum-'.$config['packagename'].'-'.$config['version'].'.'.$config['packaging'].'.txt';
837
838
	if (!file_exists($sumsfile))
839
	{
840
		echo "sha1sum file '$sumsfile' not found!\n";
841
		return;
842
	}
843
	// signing it
844
	if (empty($config['gpg']) || !file_exists($config['gpg']))
845
	{
846
		if (!empty($config['gpg'])) echo "{$config['gpg']} not found --> skipping signing sha1sum file!\n";
847
		return;
848
	}
849
	echo "Signing sha1sum file:\n";
850
	if (file_exists($sumsfile.'.asc')) unlink($sumsfile.'.asc');
851
	$cmd = $config['gpg'].' --local-user '.$config['packager'].' --clearsign '.$sumsfile;
852
	run_cmd($cmd);
853
	unlink($sumsfile);	// delete the unsigned file
854
}
855
856
/**
857
 * Create archives
858
 */
859
function do_create()
860
{
861
	global $config;
862
863
	if (!file_exists($config['sourcedir'])) mkdir($config['sourcedir'],0755,true);
864 View Code Duplication
	if (substr($config['sourcedir'],0,2) == '~/')	// sha1_file cant deal with '~/rpm'
865
	{
866
		$config['sourcedir'] = getenv('HOME').substr($config['sourcedir'],1);
867
	}
868
	$sumsfile = $config['sourcedir'].'/sha1sum-'.$config['packagename'].'-'.$config['version'].'.'.$config['packaging'].'.txt';
869
	$sums = '';
870
871
	chdir($config['egw_buildroot']);
872
873
	if($config['extra'])
874
	{
875
		$exclude = $exclude_all = array();
876
		foreach($config['extra'] as $name => $modules)
877
		{
878
			foreach((array)$modules as $module)
879
			{
880
				$exclude[] = basename($module);
881
				if (!empty($config['all-add']) && !in_array($module, $config['all-add']) && (is_int($name) || !in_array($name, $config['all-add'])))
882
				{
883
					$exclude_all[] = basename($module);
884
				}
885
			}
886
		}
887
		$exclude_extra = ' --exclude=egroupware/'.implode(' --exclude=egroupware/', $exclude);
888
		$exclude_all_extra =  $exclude_all ? ' --exclude=egroupware/'.implode(' --exclude=egroupware/', $exclude_all) : '';
889
	}
890
	foreach($config['types'] as $type)
891
	{
892
		echo "Creating $type archives\n";
893
		$tar_type = $type == 'tar.bz2' ? 'j' : 'z';
894
895
		$file = $config['sourcedir'].'/'.$config['packagename'].'-'.$config['version'].'.'.$config['packaging'].'.'.$type;
896
		switch($type)
897
		{
898
			case 'all.tar.bz2':	// single tar-ball for debian builds not easily supporting to use multiple
899
				$file = $config['sourcedir'].'/'.$config['packagename'].'-all-'.$config['version'].'.'.$config['packaging'].'.tar';
900
				$cmd = $config['tar'].' --owner=root --group=root -cf '.$file.$exclude_all_extra.' egroupware';
901
				if (!empty($config['all-add']))
902
				{
903
					foreach((array)$config['all-add'] as $add)
904
					{
905
						if (substr($add, -4) != '.tar') continue;	// probably a module
906
						if (!($tar = realpath($add))) throw new Exception("File '$add' not found!");
907
						$cmd .= '; '.$config['tar'].' --owner=root --group=root -Af '.$file.' '.$tar;
908
					}
909
				}
910
				if (file_exists($file.'.bz2')) $cmd .= '; rm -f '.$file.'.bz2';
911
				$cmd .= '; '.$config['bzip2'].' '.$file;
912
				// run cmd now and continue without adding all tar-ball to sums, as we dont want to publish it
913
				run_cmd($cmd);
914
				continue 2;
915
			case 'tar.bz2':
916 View Code Duplication
			case 'tar.gz':
917
				$cmd = $config['tar'].' --owner=root --group=root -c'.$tar_type.'f '.$file.$exclude_extra.' egroupware';
918
				break;
919
			case 'zip':
920
				$cmd = file_exists($file) ? $config['rm'].' -f '.$file.'; ' : '';
921
				$cmd .= $config['mv'].' egroupware/'.implode(' egroupware/', $exclude).' . ;';
922
				$cmd .= $config['zip'].' -q -r -9 '.$file.' egroupware ;';
923
				$cmd .= $config['mv'].' '.implode(' ', $exclude).' egroupware';
924
				break;
925
		}
926
		run_cmd($cmd);
927
		$sums .= sha1_file($file)."\t".basename($file)."\n";
928
929
		foreach($config['extra'] as $name => $modules)
930
		{
931
			if (is_numeric($name)) $name = $modules;
932
			$dirs = ' egroupware/'.implode(' egroupware/', (array)$modules);
933
			$file = $config['sourcedir'].'/'.$config['packagename'].'-'.$name.'-'.$config['version'].'.'.$config['packaging'].'.'.$type;
934
			switch($type)
935
			{
936
				case 'all.tar.bz2':
937
					break;	// nothing to do
938
				case 'tar.bz2':
939 View Code Duplication
				case 'tar.gz':
940
					$cmd = $config['tar'].' --owner=root --group=root -c'.$tar_type.'f '.$file.$dirs;
941
					break;
942
				case 'zip':
943
					$cmd = file_exists($file) ? $config['rm'].' -f '.$file.'; ' : '';
944
					$cmd .= $config['zip'].' -q -r -9 '.$file.$dirs;
945
					break;
946
			}
947
			run_cmd($cmd);
948
			$sums .= sha1_file($file)."\t".basename($file)."\n";
949
		}
950
	}
951
	// writing sha1sum file
952
	file_put_contents($sumsfile,$sums);
953
}
954
955
/**
956
 * Scan checkout for viruses, if clamscan is installed (not fatal if not!)
957
 */
958
function do_virusscan()
959
{
960
	global $config,$verbose;
961
962
	if (!file_exists($config['clamscan']) || !is_executable($config['clamscan']))
963
	{
964
		echo "Virusscanner '$config[clamscan]' not found --> skipping virus scan!\n";
965
		return;
966
	}
967
	// try updating virus database
968
	if (file_exists($config['freshclam']))
969
	{
970
		echo "Updating virus signatures\n";
971
		$cmd = '/usr/bin/sudo '.$config['freshclam'];
972
		if (!$verbose && function_exists('posix_getuid') && posix_getuid()) echo $cmd."\n";
973
		$output = null;
974
		run_cmd($cmd,$output,1);	// 1 = ignore already up to date database
975
	}
976
	echo "Starting virusscan\n";
977
	$cmd = $config['clamscan'].' --quiet -r '.$config['egw_buildroot'];
978
	run_cmd($cmd);
979
	echo "Virusscan successful (no viruses found).\n";
980
}
981
982
/**
983
 * Copy non .svn/.git parts to egw_buildroot and fix permissions and ownership
984
 *
985
 * We need to stash local modifications (currently only in egroupware main module) to revert eg. .mrconfig modifications
986
 */
987
function do_copy()
988
{
989
	global $config;
990
991
	// copy everything, but .svn dirs from checkoutdir to egw_buildroot
992
	echo "Copying non-svn/git dirs to buildroot\n";
993
994
	if (!file_exists($config['egw_buildroot']))
995
	{
996
		run_cmd("mkdir -p $config[egw_buildroot]");
997
	}
998
999
	// we need to stash uncommited changes like .mrconfig, before copying
1000
	if (file_exists($config['checkoutdir'].'/.git')) run_cmd("cd $config[checkoutdir]; git stash");
1001
1002
	try {
1003
		$cmd = '/usr/bin/rsync -r --delete --delete-excluded --exclude .svn --exclude .git\* --exclude .mrconfig --exclude node_modules/ '.$config['checkoutdir'].'/ '.$config['egw_buildroot'].'/'.$config['aliasdir'].'/';
1004
		run_cmd($cmd);
1005
	}
1006
	catch (Exception $e) {
1007
		// catch failures to pop stash, before throwing exception
1008
	}
1009
	if (file_exists($config['checkoutdir'].'/.git')) run_cmd("git stash pop");
1010
	if (isset($e)) throw $e;
1011
1012
	if (($cmd = config_translate('patchCmd')) && $cmd[0] != '#')
1013
	{
1014
		echo "Running $cmd\n";
1015
		run_cmd($cmd);
1016
	}
1017
	// fix permissions
1018
	echo "Fixing permissions\n";
1019
	chdir($config['egw_buildroot'].'/'.$config['aliasdir']);
1020
	run_cmd('/bin/chmod -R a-x,u=rwX,g=rX,o=rX .');
1021
	run_cmd('/bin/chmod +x */*cli.php phpgwapi/cron/*.php doc/rpm-build/*.php');
1022
}
1023
1024
/**
1025
 * Checkout or update EGroupware
1026
 *
1027
 * Ensures an existing checkout is from the correct branch! Otherwise it get's deleted
1028
 */
1029
function do_svncheckout()
1030
{
1031
	global $config,$svn;
1032
1033
	echo "Starting svn checkout/update\n";
1034
	if (!file_exists($config['checkoutdir']))
1035
	{
1036
		mkdir($config['checkoutdir'],0755,true);
1037
	}
1038 View Code Duplication
	elseif (!is_dir($config['checkoutdir']) || !is_writable($config['checkoutdir']))
1039
	{
1040
		throw new Exception("svn checkout directory '{$config['checkoutdir']} exists and is NO directory or NOT writable!");
1041
	}
1042
	chdir($config['checkoutdir']);
1043
1044
	// do we use a just created tag --> list of taged modules
1045
	if ($config['svntag'])
1046
	{
1047
		if (!isset($config['modules']))
1048
		{
1049
			get_modules_per_repo();
1050
		}
1051
		$config['svntag'] = config_translate('svntag');	// in case svntag command did not run, translate tag name
1052
1053
		if (file_exists($config['aliasdir']))
1054
		{
1055
			system('/bin/rm -rf .svn '.$config['aliasdir']);	// --> remove the whole checkout, as we dont implement switching tags
1056
			clearstatcache();
1057
		}
1058
		foreach($config['modules'] as $repo => $modules)
1059
		{
1060
			$cmd = $svn.' co ';
1061
			foreach($modules as $path => $url)
1062
			{
1063
				if ($path == $config['aliasdir'])
1064
				{
1065
					$cmd = $svn.' co '.$repo.'/'.$config['svntag'].'/'.$path;
1066
					run_cmd($cmd);
1067
					chdir($path);
1068
					$cmd = $svn.' co ';
1069
					continue;
1070
				}
1071
				if(file_exists($config['aliasdir']))
1072
				{
1073
					die("'egroupware' applications must be first one in externals!\n");
1074
				}
1075
				$cmd .= ' '.$repo.'/'.$config['svntag'].'/'.basename($path);
1076
			}
1077
			run_cmd($cmd);
1078
		}
1079
	}
1080
	// regular branch update, without tag
1081
	else
1082
	{
1083
		$svnbranch = $config['svnbase'].'/'.$config['svnbranch'];
1084
		if (file_exists($config['aliasdir']))
1085
		{
1086
			// check if correct branch
1087
			$cmd = 'LANG=C '.$svn.' info';
1088
			$output = $ret = null;
1089
			exec($cmd,$output,$ret);
1090
			foreach($output as $line)
0 ignored issues
show
Bug introduced by
The expression $output of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. 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:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1091
			{
1092
				if ($ret || substr($line,0,5) == 'URL: ')
1 ignored issue
show
Bug Best Practice introduced by
The expression $ret of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1093
				{
1094
					$url = substr($line,5);
1095
					if ($ret || substr($url,0,strlen($svnbranch)) != $svnbranch)	// wrong branch (or no svn dir)
1 ignored issue
show
Bug Best Practice introduced by
The expression $ret of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1096
					{
1097
						echo "Checkout is of wrong branch --> deleting it\n";
1098
						system('/bin/rm -rf .svn '.$config['aliasdir']);	// --> remove the whole checkout
1099
						clearstatcache();
1100
					}
1101
					break;
1102
				}
1103
			}
1104
		}
1105
		$url = $svnbranch.'/'.$config['svnalias'];
1106
		$cmd = $svn.' co '.$url.' .';
1107
		run_cmd($cmd);
1108
1109
		chdir($config['aliasdir']);
1110
		foreach($config['extra'] as $module)
1111
		{
1112
			$module = config_translate(null, $module);	// allow to use config vars like $svnbranch in module
1113
			$url = strpos($module,'://') === false ? $svnbranch.'/' : '';
1114
			$url .= $module;
1115
			$cmd = $svn.' co '.$url;
1116
			run_cmd($cmd);
1117
		}
1118
	}
1119
	// do composer install to fetch dependencies
1120
	if ($config['composer'])
1121
	{
1122
		run_cmd($config['composer']);
1123
	}
1124
	// run after-checkout command(s), eg. to purge source directories
1125
	run_cmd($config['after-checkout']);
1126
}
1127
1128
/**
1129
 * Get module path per svn repo from our config
1130
 *
1131
 * @return array with $repro_url => $path => $url, eg. array(
1132
 *		"svn+ssh://[email protected]/egroupware" => array(
1133
 *			"egroupware" => "svn+ssh://[email protected]/egroupware/branches/14.2/egroupware",
1134
 *			"egroupware/addressbook" => "svn+ssh://[email protected]/egroupware/branches/14.2/addressbook",
1135
 */
1136
function get_modules_per_svn_repo()
1137
{
1138
	global $config,$svn,$verbose;
1139
1140
	// process alias/externals
1141
	$svnbranch = $config['svnbase'].'/'.$config['svnbranch'];
1142
	$url = $svnbranch.'/'.$config['svnalias'];
1143
	$cmd = $svn.' propget svn:externals --strict '.$url;
1144
	if ($verbose) echo $cmd."\n";
1145
	$output = $ret = null;
1146
	exec($cmd,$output,$ret);
1147
	$config['modules'] = array();
1148
	foreach($output as $line)
0 ignored issues
show
Bug introduced by
The expression $output of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. 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:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1149
	{
1150
		$line = trim($line);
1151
		if (empty($line) || $line[0] == '#') continue;
1152
		list($path,$url) = preg_split('/[ \t\r\n]+/',$line);
1153
		$matches = null;
1154
		if (!preg_match('/([a-z+]+:\/\/[a-z@.]+\/[a-z]+)\/(branches|tags|trunk)/',$url,$matches)) die("Invalid SVN URL: $url\n");
1155
		$repo = $matches[1];
1156
		if ($repo == 'http://svn.egroupware.org/egroupware') $repo = 'svn+ssh://[email protected]/egroupware';
1157
		$config['modules'][$repo][$path] = $url;
1158
	}
1159
	// process extra modules
1160
	foreach($config['extra'] as $module)
1161
	{
1162
		$module = config_translate(null, $module);	// allow to use config vars like $svnbranch in module
1163
		$url = strpos($module,'://') === false ? $svnbranch.'/' : '';
1164
		$url .= $module;
1165
		if (strpos($module,'://') !== false) $module = basename($module);
1166
		if (!preg_match('/([a-z+]+:\/\/[a-z@.]+\/[a-z]+)\/(branches|tags|trunk)/',$url,$matches)) die("Invalid SVN URL: $url\n");
1167
		$repo = $matches[1];
1168
		if ($repo == 'http://svn.egroupware.org/egroupware') $repo = 'svn+ssh://[email protected]/egroupware';
1169
		$config['modules'][$repo][$config['aliasdir'].'/'.$module] = $url;
1170
	}
1171
	if ($verbose) print_r($config['modules']);
1172
	return $config['modules'];
1173
}
1174
1175
/**
1176
 * Create svn tag or branch
1177
 */
1178
function do_svntag()
1179
{
1180
	global $config,$svn;
1181
1182
	if (empty($config['svntag'])) return;	// otherwise we copy everything in svn root!
1183
1184
	$config['svntag'] = config_translate('svntag');	// allow to use config vars like $version in tag
1185
1186
	echo "Creating SVN tag $config[svntag]\n";
1187
	if (!isset($config['modules']))
1188
	{
1189
		get_modules_per_repo();
1190
	}
1191
	// create tags (per repo)
1192
	foreach($config['modules'] as $repo => $modules)
0 ignored issues
show
Bug introduced by
The expression $config['modules'] of type null is not traversable.
Loading history...
1193
	{
1194
		$cmd = $svn.' cp --parents -m '.escapeshellarg('Creating '.$config['svntag']).' '.implode(' ',$modules).' '.$repo.'/'.$config['svntag'].'/';
1195
		run_cmd($cmd);
1196
	}
1197
}
1198
1199
/**
1200
 * Runs given shell command, exists with error-code after echoing the output of the failed command (if not already running verbose)
1201
 *
1202
 * @param string $cmd
1203
 * @param array& $output=null $output of command, only if !$verbose !!!
1204
 * @param int|array $no_bailout =null exit code(s) to NOT bail out
1205
 * @return int exit code of $cmd
1206
 */
1207
function run_cmd($cmd,array &$output=null,$no_bailout=null)
1208
{
1209
	global $verbose;
1210
1211
	if ($verbose && func_num_args() == 1)
1212
	{
1213
		echo $cmd."\n";
1214
		$ret = null;
1215
		system($cmd,$ret);
1216
	}
1217
	else
1218
	{
1219
		$output[] = $cmd;
1220
		exec($cmd,$output,$ret);
1221
		if ($verbose) echo implode("\n",$output)."\n";
1222
	}
1223
	if ($ret && !in_array($ret,(array)$no_bailout))
1 ignored issue
show
Bug Best Practice introduced by
The expression $ret of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1224
	{
1225
		if (!$verbose) echo implode("\n",$output)."\n";
1226
		throw new Exception("Error during '$cmd' --> aborting",$ret);
1227
	}
1228
	return $ret;
1229
}
1230
1231
/**
1232
 * Format array or other types as (one-line) string, eg. for error_log statements
1233
 *
1234
 * @param mixed $var variable to dump
1235
 * @return string
1236
 */
1237 View Code Duplication
function array2string($var)
1 ignored issue
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1238
{
1239
	switch (($type = gettype($var)))
1240
	{
1241
		case 'boolean':
1242
			return $var ? 'TRUE' : 'FALSE';
1243
		case 'string':
1244
			return "'$var'";
1245
		case 'integer':
1246
		case 'double':
1247
		case 'resource':
1248
			return $var;
1249
		case 'NULL':
1250
			return 'NULL';
1251
		case 'object':
1252
		case 'array':
1253
			return str_replace(array("\n",'    '/*,'Array'*/),'',print_r($var,true));
1254
	}
1255
	return 'UNKNOWN TYPE!';
1256
}
1257
1258
/**
1259
 * Give usage information and an optional error-message, before stoping program execution with exit-code 90 or 0
1260
 *
1261
 * @param string $error =null optional error-message
1262
 */
1263
function usage($error=null)
0 ignored issues
show
Best Practice introduced by
The function usage() has been defined more than once; this definition is ignored, only the first definition in admin/admin-cli.php (L305-338) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
1264
{
1265
	global $prog,$config;
1266
1267
	echo "Usage: $prog [-h|--help] [-v|--verbose] [options, ...]\n\n";
1268
	echo "options and their defaults:\n";
1269
	unset($config['modules']);	// they give an error, because of nested array and are quite lengthy
1270
	foreach($config as $name => $default)
1271
	{
1272
		if (is_array($default)) $default = implode(' ',$default);
1273
		echo '--'.str_pad($name,20).$default."\n";
1274
	}
1275
	if ($error)
1 ignored issue
show
Bug Best Practice introduced by
The expression $error of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1276
	{
1277
		echo "$error\n\n";
1278
		exit(90);
1279
	}
1280
	exit(0);
1281
}
1282