jcb-compiler/src/23f2ca33-440a-4941-8e9a-4bc.../code.php

214 lines
6.0 KiB
PHP

<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Utilities;
use VDM\Joomla\Componentbuilder\Compiler\Power\Injector as Power;
use VDM\Joomla\Utilities\MathHelper;
/**
* File Injector
* Thanks to http://stackoverflow.com/a/16813550/1429677
*
* @since 3.2.0
*/
final class FileInjector
{
/**
* The Injector Class.
*
* @var Power
* @since 3.2.0
*/
protected Power $power;
/**
* The pattern to get the powers
*
* @var string
* @since 3.2.0
**/
protected string $pattern = '/Super_'.'_'.'_[a-zA-Z0-9_]+_'.'_'.'_Power/';
/**
* Constructor.
*
* @param Power $power The Injector Class.
*
* @since 3.2.0
*/
public function __construct(Power $power)
{
$this->power = $power;
}
/**
* Inserts or replaces data in a file at a specific position.
*
* @param string $file The path of the file to modify.
* @param string $data The data to insert or replace.
* @param int $position The position in the file where the data should be inserted or replaced.
* @param int|null $replace The number of bytes to replace; if null, data will be inserted.
*
* @return void
* @throws \RuntimeException If unable to open or modify the file.
* @throws \InvalidArgumentException If the position is negative.
* @since 3.2.0
*/
public function add(string $file, string $data, int $position, ?int $replace = null): void
{
if ($position < 0)
{
throw new \InvalidArgumentException('Position cannot be negative.');
}
$found_super_powers = preg_match($this->pattern, $data);
$actual_file = $this->openFileWithLock($file);
try
{
$temp_file = fopen('php://temp', "rw+");
if ($temp_file === false)
{
throw new \RuntimeException("Unable to open temporary file.");
}
$this->processFile($actual_file, $temp_file, $data, $position, $replace);
if ($found_super_powers)
{
$this->injectSuperPowers($actual_file);
}
}
finally
{
flock($actual_file, LOCK_UN);
fclose($actual_file);
if (isset($temp_file))
{
fclose($temp_file);
}
}
}
/**
* Opens a file and acquires an exclusive lock on it.
*
* @param string $file The file path to open.
*
* @return resource The file handle.
* @throws \RuntimeException If the file cannot be opened or locked.
* @since 3.2.0
*/
private function openFileWithLock(string $file)
{
$actual_file = fopen($file, "rw+");
if ($actual_file === false || !flock($actual_file, LOCK_EX))
{
throw new \RuntimeException("Unable to open and lock the file: {$file}");
}
return $actual_file;
}
/**
* Processes the file for data insertion and copying the remaining data.
*
* @param resource $actual_file The file handle of the actual file.
* @param resource $temp_file The file handle of the temporary file.
* @param string $data The data to be inserted.
* @param int $position The position in the file for the data insertion.
* @param int|null $replace The number of bytes to replace; if null, data will be inserted.
*
* @return void
* @since 3.2.0
*/
private function processFile($actual_file, $temp_file, string $data, int $position, ?int $replace): void
{
// Make a copy of the file in the temporary stream
stream_copy_to_stream($actual_file, $temp_file);
// Move to the position where the data should be added
fseek($actual_file, $position);
// Add the data
fwrite($actual_file, $data);
$this->truncateIfNeeded($actual_file, $data, $position);
$this->copyRemainingData($actual_file, $temp_file, $position, $replace);
}
/**
* Truncates the file after data insertion if necessary.
*
* @param resource $actual_file The file handle.
* @param string $data The data that was inserted.
* @param int $position The position where data was inserted.
*
* @return void
* @since 3.2.0
*/
private function truncateIfNeeded($actual_file, string $data, int $position): void
{
// Truncate the file at the end of the added data if replacing
$data_length = mb_strlen($data, '8bit');
$remove = MathHelper::bc('add', $position, $data_length);
ftruncate($actual_file, $remove);
}
/**
* Copies the remaining data from the temporary stream to the actual file.
*
* @param resource $actual_file The file handle of the actual file.
* @param resource $temp_file The file handle of the temporary file.
* @param int $position The position in the file where data insertion finished.
* @param int|null $replace The number of bytes that were replaced; if null, data was inserted.
*
* @return void
* @since 3.2.0
*/
private function copyRemainingData($actual_file, $temp_file, int $position, ?int $replace): void
{
// check if this was a replacement of data
$position = MathHelper::bc('add', $position, $replace ?: 0);
// Move to the position of the remaining data in the temporary stream
fseek($temp_file, $position);
// Copy the remaining data from the temporary stream to the file
stream_copy_to_stream($temp_file, $actual_file);
}
/**
* Injects super powers into the file content, if found, and updates the file.
*
* @param resource $actual_file The file handle of the actual file.
*
* @return void
* @since 3.2.0
*/
private function injectSuperPowers($actual_file): void
{
rewind($actual_file);
$power_data = $this->power->power(
stream_get_contents($actual_file)
);
ftruncate($actual_file, 0);
rewind($actual_file);
fwrite($actual_file, $power_data);
}
}