licenseText = <<. * */ EOD; $this->licenseTextLegacy = << * */ EOD; $this->licenseTextLegacy = str_replace('@YEAR@', date('Y'), $this->licenseTextLegacy); } /** * @param string|string[] $folder * @param string|bool $gitRoot */ public function exec($folder, $gitRoot = false) { if (is_array($folder)) { foreach ($folder as $f) { $this->exec($f, $gitRoot); } return; } if ($gitRoot !== false && substr($gitRoot, -1) !== '/') { $gitRoot .= '/'; } if (is_file($folder)) { $this->handleFile($folder, $gitRoot); $this->printFilesToCheck(); return; } $excludes = array_map(function ($item) use ($folder) { return $folder . '/' . $item; }, ['vendor', '3rdparty', '.git', 'l10n', 'templates', 'composer', 'js', 'node_modules']); $iterator = new RecursiveDirectoryIterator($folder, RecursiveDirectoryIterator::SKIP_DOTS); $iterator = new RecursiveCallbackFilterIterator($iterator, function ($item) use ($folder, $excludes) { /** @var SplFileInfo $item */ foreach ($excludes as $exclude) { if (substr($item->getPath(), 0, strlen($exclude)) === $exclude) { return false; } } return true; }); $iterator = new RecursiveIteratorIterator($iterator); $iterator = new RegexIterator($iterator, '/^.+\.(js|php)$/i'); foreach ($iterator as $file) { /** @var SplFileInfo $file */ $this->handleFile($file, $gitRoot); } $this->printFilesToCheck(); } public function writeAuthorsFile() { ksort($this->authors); $template = 'Nextcloud is written by: @AUTHORS@ With help from many libraries and frameworks including: Open Collaboration Services SabreDAV jQuery … '; $authors = implode(PHP_EOL, array_map(function ($author) { return ' - ' . $author; }, $this->authors)); $template = str_replace('@AUTHORS@', $authors, $template); file_put_contents(__DIR__ . '/../AUTHORS', $template); } public function handleFile($path, $gitRoot) { $isPhp = preg_match('/^.+\.php$/i', $path); $source = file_get_contents($path); if ($this->isMITLicensed($source)) { echo "MIT licensed file: $path" . PHP_EOL; return; } $copyrightNotices = $this->getCopyrightNotices($path, $source); $authors = $this->getAuthors($path, $gitRoot); if ($this->isOwnCloudLicensed($source)) { $license = str_replace('@AUTHORS@', $authors, $this->licenseTextLegacy); $this->checkCopyrightState($path, $gitRoot); } else { $license = str_replace('@AUTHORS@', $authors, $this->licenseText); } if ($copyrightNotices === '') { $creator = $this->getCreatorCopyright($path, $gitRoot); $license = str_replace('@COPYRIGHT@', $creator, $license); } else { $license = str_replace('@COPYRIGHT@', $copyrightNotices, $license); } [$source, $isStrict] = $this->eatOldLicense($source); if ($isPhp) { if ($isStrict) { $source = 'getTimestamp(); $buildDir = getcwd(); if ($gitRoot) { chdir($gitRoot); $path = substr($path, strlen($gitRoot)); } $out = shell_exec("git --no-pager blame --line-porcelain $path | sed -n 's/^author-time //p'"); if ($gitRoot) { chdir($buildDir); } $timestampChanges = explode(PHP_EOL, $out); $timestampChanges = array_slice($timestampChanges, 0, count($timestampChanges) - 1); foreach ($timestampChanges as $timestamp) { if ((int)$timestamp < $deadlineTimestamp) { return; } } //all changes after the deadline $this->checkFiles[] = $path; } private function printFilesToCheck() { if (!empty($this->checkFiles)) { print "\n"; print 'For following files all lines changed since the Nextcloud fork.' . PHP_EOL; print 'Please check if these files can be moved over to AGPLv3 or later' . PHP_EOL; print "\n"; foreach ($this->checkFiles as $file) { print $file . PHP_EOL; } print "\n"; } } private function filterAuthors($authors = []) { $authors = array_filter($authors, function ($author) { return !in_array($author, [ '', 'Not Committed Yet ', 'Jenkins for ownCloud ', 'Scrutinizer Auto-Fixer ', ]); }); // Strip out dependabot $authors = array_filter($authors, function ($author) { return strpos($author, 'dependabot') === false; }); return $authors; } private function getCreatorCopyright($file, $gitRoot) { $buildDir = getcwd(); if ($gitRoot) { chdir($gitRoot); $file = substr($file, strlen($gitRoot)); } $year = trim(shell_exec('date +%Y -d "$(git log --format=%aD ../apps/files/lib/Controller/ViewController.php | tail -1)"')); $blame = shell_exec("git blame --line-porcelain $file | sed -n 's/^author //p;s/^author-mail //p' | sed 'N;s/\\n/ /'"); $authors = explode(PHP_EOL, $blame); if ($gitRoot) { chdir($buildDir); } $authors = $this->filterAuthors($authors); if ($gitRoot) { $authors = array_map([$this, 'checkCoreMailMap'], $authors); $authors = array_unique($authors); } $creator = array_key_exists(0, $authors) ? $this->fixInvalidEmail($authors[0]) : ''; return " * @copyright Copyright (c) $year $creator"; } private function getAuthors($file, $gitRoot) { // only add authors that changed code and not the license header $licenseHeaderEndsAtLine = trim(shell_exec("grep -n '*/' $file | head -n 1 | cut -d ':' -f 1")); $buildDir = getcwd(); if ($gitRoot) { chdir($gitRoot); $file = substr($file, strlen($gitRoot)); } $out = shell_exec("git blame --line-porcelain -L $licenseHeaderEndsAtLine, $file | sed -n 's/^author //p;s/^author-mail //p' | sed 'N;s/\\n/ /' | sort -f | uniq"); if ($gitRoot) { chdir($buildDir); } $authors = explode(PHP_EOL, $out); $authors = $this->filterAuthors($authors); if ($gitRoot) { $authors = array_map([$this, 'checkCoreMailMap'], $authors); $authors = array_unique($authors); } $authors = array_map(function ($author) { $author = $this->fixInvalidEmail($author); $this->authors[$author] = $author; return " * @author $author"; }, $authors); return implode(PHP_EOL, $authors); } private function checkCoreMailMap($author) { if (empty($this->mailMap)) { $content = file_get_contents(__DIR__ . '/../.mailmap'); $entries = explode("\n", $content); foreach ($entries as $entry) { if (strpos($entry, '> ') === false) { $this->mailMap[$entry] = $entry; } else { [$use, $actual] = explode('> ', $entry); $this->mailMap[$actual] = $use . '>'; } } } if (isset($this->mailMap[$author])) { return $this->mailMap[$author]; } return $author; } private function fixInvalidEmail($author) { preg_match('/<(.*)>/', $author, $mailMatch); if (count($mailMatch) === 2 && !filter_var($mailMatch[1], FILTER_VALIDATE_EMAIL)) { $author = str_replace('<' . $mailMatch[1] . '>', '"' . $mailMatch[1] . '"', $author); } return $author; } } $licenses = new Licenses; if (isset($argv[1])) { $licenses->exec($argv[1], isset($argv[2]) ? $argv[1] : false); } else { $licenses->exec([ '../apps/admin_audit', '../apps/cloud_federation_api', '../apps/comments', '../apps/contactsinteraction', '../apps/dashboard', '../apps/dav', '../apps/encryption', '../apps/federatedfilesharing', '../apps/federation', '../apps/files', '../apps/files_external', '../apps/files_sharing', '../apps/files_trashbin', '../apps/files_versions', '../apps/lookup_server_connector', '../apps/oauth2', '../apps/provisioning_api', '../apps/settings', '../apps/sharebymail', '../apps/systemtags', '../apps/testing', '../apps/theming', '../apps/twofactor_backupcodes', '../apps/updatenotification', '../apps/user_ldap', '../apps/user_status', '../apps/weather_status', '../apps/workflowengine', '../build/integration/features/bootstrap', '../core', '../lib', '../ocs', '../console.php', '../cron.php', '../index.php', '../public.php', '../remote.php', '../status.php', '../version.php', ]); $licenses->writeAuthorsFile(); }