refactor: Grande migration de fichiers...

- Les fichiers de compilations sont dans compile/
- Le Phar ne présente que les fichiers de public/
- Autochargement de PclZip si besoin, avec Composer
- Correction http_no_cache qui ne créait pas le bon format de headers pour Response
- Correction CSS du pied de page si pas de version (cas des erreurs)
pull/27/head
Matthieu Marcillaud 3 months ago
parent 83f467b61f
commit be6ad0e4ba

7
.gitignore vendored

@ -1,8 +1,9 @@
/loader/spip_loader_list.json
/loader/spip_loader_config.php
/loader/public/*
!/loader/public/assets/
!/loader/public/index.php
/loader/version.php
/loader/vendor/
/public_html/
/build/
/vendor/
/composer.lock
/.php_cs.cache

@ -1,145 +1,30 @@
#!/usr/bin/env php
<?php
namespace Spip\Loader\Compiler;
use CallbackFilterIterator;
use DateTimeImmutable;
use DateTimeZone;
use FilesystemIterator;
use Iterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Seld\PharUtils\Timestamps;
use Spip\Loader\Compiler\Build;
use League\CLImate\CLImate;
error_reporting(E_ALL);
require_once 'vendor/autoload.php';
class Git {
public function getVersion(): string {
return trim(exec('git describe --tags --abbrev=0'));
}
public function getVersionDate(): DateTimeImmutable {
$date = trim(exec('git log -n1 --pretty=%ci HEAD'));
$date = new DateTimeImmutable($date ?: null);
$date->setTimezone(new DateTimeZone('UTC'));
return $date;
}
}
class Site {
public function __construct(
private string $siteDirectory,
private string $assetsDirectory,
) {}
public function prepare(): void {
if (!is_dir($this->siteDirectory)) {
mkdir($this->siteDirectory);
}
foreach ([
'spip_loader.php',
'index.html',
'logo-spip.png',
'titre_logo_installer.svg',
'favicon_rose.ico',
'spip_loader.phar',
'.htaccess',
] as $clean_file) {
if (file_exists($this->siteDirectory . '/' . $clean_file)) {
unlink($this->siteDirectory . '/' . $clean_file);
}
};
}
public function putVersion(string $version) {
file_put_contents($this->siteDirectory . '/version', $version);
}
public function copyAssets() {
$assets = new FilesystemIterator(
$this->assetsDirectory,
FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
);
$assets = array_keys(iterator_to_array($assets));
foreach ($assets as $asset) {
copy($asset, $this->siteDirectory . '/' . basename($asset));
}
}
}
class Phar {
public function __construct(
private string $siteDirectory,
private string $sourceDirectory,
private string $filename = 'spip_loader.phar'
) {}
public function putVersionInSourceDirectory(string $version) {
file_put_contents($this->sourceDirectory . '/version.php', '<?php return \'' . $version . '\';');
}
public function getPhpFiles(): Iterator {
$iterator = new RecursiveDirectoryIterator(
$this->sourceDirectory,
RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
);
$iterator = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
$iterator = new CallbackFilterIterator($iterator, function($file) {
return $file->isFile() && (
$file->getExtension() === 'php'
or $file->getExtension() === 'php8' # 1.19.0 de symfony/polyfill-mbstring/Resources/mb_convert_variables.php8
);
});
return $iterator;
}
public function getAssetsFiles(): Iterator {
return new FilesystemIterator(
$this->sourceDirectory . '/assets',
FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
);
}
public function build(DateTimeImmutable $versionDate) {
$file = $this->siteDirectory . '/' . $this->filename;
$p = new \Phar($file, 0, $this->filename);
$p->setSignatureAlgorithm(\Phar::SHA512);
$p->startBuffering();
$p->setStub(file_get_contents('stub.php'));
foreach ($this->getPhpFiles() as $phpFile) {
$_file = str_replace($this->sourceDirectory . '/', '', $phpFile);
$p[$_file] = file_get_contents($phpFile);
$p[$_file]->compress(\Phar::GZ);
}
$p->buildFromIterator($this->getAssetsFiles(), $this->sourceDirectory);
$p->stopBuffering();
#$p->compressFiles(\Phar::GZ); # casse les assets
$util = new Timestamps($file);
$util->updateTimestamps($versionDate);
$util->save($file, \Phar::SHA512);
}
public function renameTo(string $filename) {
rename($this->siteDirectory . '/spip_loader.phar', $this->siteDirectory . '/' . $filename);
}
$build = new Build(
sourceDirectory: __DIR__ . '/loader',
buildDirectory: __DIR__ . '/build',
pharFilename: 'spip_loader.php'
);
$build->prepare();
$build->run();
$climate = new CLImate;
$climate->out('');
$climate->underline()->out('Results');
$climate->out('');
foreach($build->getStats() as $item => $value) {
$climate->infoInline("$item: ");
$climate->out($value);
}
$git = new Git();
$site = new Site(__DIR__ . '/public_html', __DIR__ . '/assets');
$site->prepare();
$site->putVersion($git->getVersion());
$site->copyAssets();
$phar = new Phar(__DIR__ . '/public_html', __DIR__ . '/loader');
$phar->putVersionInSourceDirectory($git->getVersion());
$phar->build($git->getVersionDate());
$phar->renameTo('spip_loader.php');
$climate->out('');
exit(0);

@ -0,0 +1,97 @@
<?php
namespace Spip\Loader\Compiler;
use SebastianBergmann\Timer\Duration;
use SebastianBergmann\Timer\ResourceUsageFormatter;
use SebastianBergmann\Timer\Timer;
class Build {
private array $stats = [];
public function __construct(
private string $sourceDirectory,
private string $buildDirectory,
private string $pharFilename,
private Timer $timer = new Timer(),
) {
}
public function prepare(): void {
if (!is_dir($this->buildDirectory)) {
mkdir($this->buildDirectory);
}
foreach ([
'spip_loader.php',
'version',
] as $clean_file) {
if (file_exists($this->buildDirectory . '/' . $clean_file)) {
unlink($this->buildDirectory . '/' . $clean_file);
}
};
}
public function run() {
$this->start();
$git = new Git(dirname(__DIR__));
$phar = new PharArchive(
sourceDirectory: $this->sourceDirectory,
buildDirectory: $this->buildDirectory,
);
$n = $phar->build(
$git->getVersion(),
$git->getVersionDate()
);
$this->stats['Nb files'] = $n;
$phar->renameTo($this->pharFilename);
file_put_contents($this->buildDirectory . '/version', $git->getVersion());
$this->stop();
}
public function getStats(): array {
return $this->stats;
}
private function start() {
$this->stats = [
'Version' => 'No version file',
'Time' => '',
'Memory' => '',
'Filesize' => 'No archive file',
'Nb files' => '',
];
$this->timer->start();
}
private function stop() {
$this->saveStats($this->timer->stop());
}
private function saveStats(Duration $duration) {
[$time, $memory] = $this->parseResourceUsage($duration);
$this->stats['Time'] = $time;
$this->stats['Memory'] = $memory;
$pharPath = $this->buildDirectory . '/' . $this->pharFilename;
if (file_exists($pharPath)) {
$this->stats['Filesize'] = $this->MakeReadable(filesize($pharPath));
}
if (file_exists($this->buildDirectory . '/version')) {
$this->stats['Version'] = file_get_contents($this->buildDirectory . '/version');
}
}
private function parseResourceUsage(Duration $duration): array {
$usage = (new ResourceUsageFormatter)->resourceUsage($duration);
[$time, $memory] = explode(', ', $usage, 2);
return [
explode(': ', $time)[1],
explode(': ', $memory)[1],
];
}
private function MakeReadable($bytes) {
$i = floor(log($bytes, 1024));
return round($bytes / pow(1024, $i), [0,0,2,2,3][$i]).['B','kiB','MiB','GiB','TiB'][$i];
}
}

@ -0,0 +1,30 @@
<?php
namespace Spip\Loader\Compiler;
use DateTimeImmutable;
use DateTimeZone;
class Git {
public function __construct(private string $directory = __DIR__) {}
public function setDirectory(string $directory) {
$this->directory = $directory;
}
public function run(string $command): string {
$git = 'git -C ' . $this->directory;
return trim(exec($git . ' ' . $command));
}
public function getVersion(): string {
return $this->run('describe --tags --abbrev=0');
}
public function getVersionDate(): DateTimeImmutable {
$date = $this->run('log -n1 --pretty=%ci HEAD');
$date = new DateTimeImmutable($date ?: null);
$date->setTimezone(new DateTimeZone('UTC'));
return $date;
}
}

@ -0,0 +1,118 @@
<?php
namespace Spip\Loader\Compiler;
use CallbackFilterIterator;
use Composer\Pcre\Preg;
use FilesystemIterator;
use Iterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Seld\PharUtils\Timestamps;
class PharArchive {
private const FILENAME = 'spip_loader.phar';
public function __construct(
private string $sourceDirectory,
private string $buildDirectory,
private string $filename = self::FILENAME
) {}
public function getPhpFiles(): Iterator {
$iterator = new RecursiveDirectoryIterator(
$this->sourceDirectory,
RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
);
$iterator = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
$iterator = new CallbackFilterIterator($iterator, function($file) {
/** @var \SplFileInfo $file */
if (!$file->isFile()) {
return false;
}
if (!in_array($file->getExtension(), ['php', 'php8'])) {
return false;
}
if ($file->getPathName() === $this->sourceDirectory . '/phar/stub.php') {
return false;
}
return true;
});
return $iterator;
}
public function getAssetsFiles(): Iterator {
return new FilesystemIterator(
$this->sourceDirectory . '/public/assets',
FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS
);
}
/** @return int Nombre de fichiers insérés dans larchive */
public function build(
string $version,
\DateTimeInterface $versionDate,
): int {
$file = $this->buildDirectory . '/' . $this->filename;
$p = new \Phar($file, 0, $this->filename);
$p->setSignatureAlgorithm(\Phar::SHA512);
$p->startBuffering();
$p->setStub(file_get_contents($this->sourceDirectory . '/phar/stub.php'));
$p['/version.php'] = "<?php return '$version';";
$p['/index.php'] = "<?php require 'public/index.php';";
foreach ($this->getPhpFiles() as $phpFile) {
$_file = str_replace($this->sourceDirectory . '/', '', $phpFile);
$p[$_file] = $this->stripWhitespace(file_get_contents($phpFile));
$p[$_file]->compress(\Phar::GZ);
}
$p->buildFromIterator($this->getAssetsFiles(), $this->sourceDirectory);
$p->stopBuffering();
#$p->compressFiles(\Phar::GZ); # casse les assets
$nb = $p->count();
$util = new Timestamps($file);
$util->updateTimestamps($versionDate);
$util->save($file, \Phar::SHA512);
return $nb;
}
public function renameTo(string $filename) {
rename($this->buildDirectory . '/' . self::FILENAME, $this->buildDirectory . '/' . $filename);
}
/**
* Removes whitespace from a PHP source string while preserving line numbers.
*
* @param string $source A PHP string
* @return string The PHP string with the whitespace removed
*/
private function stripWhitespace(string $source): string
{
if (!function_exists('token_get_all')) {
return $source;
}
$output = '';
foreach (token_get_all($source) as $token) {
if (is_string($token)) {
$output .= $token;
} elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) {
$output .= str_repeat("\n", substr_count($token[1], "\n"));
} elseif (T_WHITESPACE === $token[0]) {
// reduce wide spaces
$whitespace = Preg::replace('{[ \t]+}', ' ', $token[1]);
// normalize newlines to \n
$whitespace = Preg::replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
// trim leading spaces
$whitespace = Preg::replace('{\n +}', "\n", $whitespace);
$output .= $whitespace;
} else {
$output .= $token[1];
}
}
return $output;
}
}

@ -1,12 +1,20 @@
{
"require": {
"composer/pcre": "*",
"seld/phar-utils": "^1.2"
"seld/phar-utils": "^1.2",
"phpunit/php-timer": "^6.0",
"league/climate": "^3.8"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"phpstan/phpstan": "^1.10",
"spip/coding-standards": "^1.2"
"spip/coding-standards": "^1.2",
"symfony/var-dumper": "^6.2"
},
"autoload": {
"psr-4": {
"Spip\\Loader\\Compiler\\": "compiler/"
}
},
"autoload-dev": {
"file": [

@ -6,7 +6,7 @@ use Spip\Loader\Config\Custom;
use Spip\Loader\Config\Internal;
use Spip\Loader\Http\Request;
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/../vendor/autoload.php';
$request = Request::createFromGlobals();
$config = new Config(
@ -29,5 +29,16 @@ echo <<<TEXT
$title
$line
TEXT;
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__DIR__)));
$files = array();
/** @var SplFileInfo $file */
foreach ($rii as $file) {
if ($file->isDir()){
continue;
}
echo $file->getPathname() . "\n";
}

@ -5,7 +5,10 @@
"autoload": {
"psr-4": {
"Spip\\Loader\\": "src/"
}
},
"psr-0": {
"PclZip": "lib/"
}
},
"config": {
"platform": {

@ -1,7 +1,10 @@
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL ^ E_DEPRECATED);
if (!http_response_code()) {
require_once 'phar://' . __FILE__ . '/cli.php';
require_once 'phar://' . __FILE__ . '/bin/console.php';
exit(0);
}
@ -21,7 +24,10 @@ Phar::webPhar(
# du cache dessus
header('Expires: ' . \date('r', time() + 86400 * 7));
}
if ($extension === 'php' and $path !== '/index.php') {
if ($extension && $extension !== 'php') {
return '/public' . $path;
}
if ($path && $path !== '/index.php') {
return false;
}
return $path;

@ -50,7 +50,7 @@
margin-bottom: -1em;
}
.app-version:before {
.app-version:not(:empty):before {
content: " • ";
}

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

@ -13,7 +13,7 @@ use Spip\Loader\Template\Utils;
#error_reporting(E_ALL);
#ini_set('display_errors', 1);
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/../vendor/autoload.php';
try {
$request = Request::createFromGlobals();

@ -27,11 +27,9 @@ class Extensions implements CheckInterface {
return false;
}
$pclzip = __DIR__ . '/../../pclzip.php';
if (!file_exists($pclzip)) {
if (!class_exists(\PclZip::class)) {
return false;
}
include $pclzip;
return true;
}

@ -57,22 +57,23 @@ final class Internal extends AbstractConfig {
* We save 'script_url' to send a <base href="xxx"> information to the browser.
*
* @param string $filepath Local path of the script `index.php`
* - inside phar : phar:///[...]/public_html/spip_loader.php/index.php
* - outside phar : /[..]/opt/spip_loader/loader/index.php
* - inside phar : phar:///[...]/build/spip_loader.php/index.php
* - outside phar : /[..]/opt/spip_loader/loader/public/index.php
* @param string $script_filename Local path of the primary script
* - inside phar : /[...]/opt/spip_loader/public_html/spip_loader.php
* - outside phar : /[...]/opt/spip_loader/loader/index.php
* - inside phar : /[...]/opt/spip_loader/build/spip_loader.php
* - outside phar : /[...]/opt/spip_loader/loader/public/index.php
* @param string $script_url Url of the spip_loader.php script
* - inside phar : //loader.spip.test/public_html/spip_loader.php/index.php
* - outside phar : //loader.spip.test/loader/index.php
* - inside phar : //loader.spip.test/build/spip_loader.php/index.php
* - outside phar : //loader.spip.test/loader/public/index.php
*/
public function __construct($filepath, $script_filename, $script_url) {
$this->set('app.filename', basename($script_url));
$this->set('phar.filename', basename(\Phar::running(false)));
$this->set('directory.root', dirname($filepath));
$this->set('directory.work', dirname($script_filename));
$this->set('directory.root', dirname(dirname($filepath)));
$this->set('directory.work', dirname(dirname($script_filename)));
$this->set('url.frontend', dirname($script_url) . '/');
# $this->set('url.root', dirname($script_url, \Phar::running(false) ? 2 : 1) . '/'); # php 7.0+
$this->set('url.root', (\Phar::running(false) ? dirname(dirname($script_url)) : dirname($script_url)) . '/');
#dump($filepath, $script_filename, $script_url, $this->config);
}
}

@ -69,7 +69,6 @@ class ArchiveClean extends AbstractRoute implements RouteInterface {
}
}
include __DIR__ . '/../../pclzip.php';
$zip = new \PclZip($fichier);
$content = $zip->listContent();

@ -54,7 +54,6 @@ class ArchiveDecompress extends AbstractRoute implements RouteInterface {
@mkdir($tmp = $tmp_directory . 'zip_' . md5($fichier), $chmod);
$ok = is_dir($tmp);
include __DIR__ . '/../../pclzip.php';
$zip = new \PclZip($fichier);
$content = $zip->listContent();
$max_index = count($content);

@ -12,11 +12,11 @@ class Utils {
*/
public static function http_no_cache() {
return [
'Content-Type', 'text/html; charset=utf-8',
'Expires', '0',
'Last-Modified', '' . gmdate('D, d M Y H:i:s') . ' GMT',
'Cache-Control', 'no-cache, must-revalidate',
'Pragma', 'no-cache',
'Content-Type' => 'text/html; charset=utf-8',
'Expires' => '0',
'Last-Modified' => '' . gmdate('D, d M Y H:i:s') . ' GMT',
'Cache-Control' => 'no-cache, must-revalidate',
'Pragma' => 'no-cache',
];
}

Loading…
Cancel
Save