<commit_before><?php
/**
 * PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
 * 
 * @package Shapefile
 * @author  <NAME>
 * @version 3.0.2
 * @license MIT
 * @link    https://gasparesganga.com/labs/php-shapefile/
 */

namespace Shapefile;

class ShapefileWriter extends Shapefile
{
    /** SHP pack methods hash */
    private static $shp_pack_methods = [
        Shapefile::SHAPE_TYPE_NULL          => 'packNull',
        Shapefile::SHAPE_TYPE_POINT         => 'packPoint',
        Shapefile::SHAPE_TYPE_POLYLINE      => 'packPolyLine',
        Shapefile::SHAPE_TYPE_POLYGON       => 'packPolygon',
        Shapefile::SHAPE_TYPE_MULTIPOINT    => 'packMultiPoint',
        Shapefile::SHAPE_TYPE_POINTZ        => 'packPointZ',
        Shapefile::SHAPE_TYPE_POLYLINEZ     => 'packPolyLineZ',
        Shapefile::SHAPE_TYPE_POLYGONZ      => 'packPolygonZ',
        Shapefile::SHAPE_TYPE_MULTIPOINTZ   => 'packMultiPointZ',
        Shapefile::SHAPE_TYPE_POINTM        => 'packPointM',
        Shapefile::SHAPE_TYPE_POLYLINEM     => 'packPolyLineM',
        Shapefile::SHAPE_TYPE_POLYGONM      => 'packPolygonM',
        Shapefile::SHAPE_TYPE_MULTIPOINTM   => 'packMultiPointM',
    ];
    
    /** Buffered file types */
    private static $buffered_files = [
        Shapefile::FILE_SHP,
        Shapefile::FILE_SHX,
        Shapefile::FILE_DBF,
        Shapefile::FILE_DBT,
    ];
    
    
    /**
     * @var array   File writing buffers.
     */
    private $buffers = [];
    
    /**
     * @var integer Buffered records count.
     */
    private $buffered_record_count = 0;
     
    /**
     * @var integer Number of records.
     */
    private $tot_records = 0;
     
    /**
     * @var integer Current offset in SHP file and buffer (in 16-bit words).
     *              First 50 16-bit are reserved for file header.
     */
    private $shp_current_offset = 50; 
    
    /**
     * @var integer Next available block in DBT file.
     */
    private $dbt_next_available_block = 0;
    
    /**
     * @var bool    Flag representing whether file headers have been initialized or not.
     */
    private $flag_init_headers = false;
    
    
    
    /////////////////////////////// PUBLIC ///////////////////////////////
    /**
     * Constructor.
     * 
     * @param   string|array    $files      Path to SHP file / Array of paths / Array of handles of individual files.
     * @param   array           $options    Optional associative array of options.
     */
    public function __construct($files, $options = [])
    {
        // Options
        $this->initOptions([
            Shapefile::OPTION_BUFFERED_RECORDS,
            Shapefile::OPTION_CPG_ENABLE_FOR_DEFAULT_CHARSET,
            Shapefile::OPTION_DBF_FORCE_ALL_CAPS,
            Shapefile::OPTION_DBF_NULL_PADDING_CHAR,
            Shapefile::OPTION_DBF_NULLIFY_INVALID_DATES,
            Shapefile::OPTION_DELETE_EMPTY_FILES,
            Shapefile::OPTION_ENFORCE_GEOMETRY_DATA_STRUCTURE,
            Shapefile::OPTION_EXISTING_FILES_MODE,
            Shapefile::OPTION_SUPPRESS_M,
            Shapefile::OPTION_SUPPRESS_Z,
        ], $options);
        
        // Open files
        $this->openFiles($files, true);
        
        // Init Buffers
        $this->buffers = array_fill_keys(array_intersect(self::$buffered_files, array_keys($this->getFiles())), '');  
        
        // Mode overwrite
        if ($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_OVERWRITE) {
            foreach (array_keys($this->getFiles()) as $file_type) {
                if ($this->getFileSize($file_type) > 0) {
                    $this->fileTruncate($file_type);
                    $this->setFilePointer($file_type, 0);
                }
            }
        }
        
        // Mode append
        if ($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_APPEND && $this->getFileSize(Shapefile::FILE_SHP) > 0) {        
            // Open Shapefile
            $ShapefileReader = new ShapefileReader($this->getFiles(), [
                Shapefile::OPTION_DBF_CONVERT_TO_UTF8   => false,
                Shapefile::OPTION_DBF_FORCE_ALL_CAPS    => $this->getOption(Shapefile::OPTION_DBF_FORCE_ALL_CAPS),
                Shapefile::OPTION_DBF_IGNORED_FIELDS    => [],
                Shapefile::OPTION_IGNORE_SHAPEFILE_BBOX => false,
            ]);
            // Shape type
            $this->setShapeType($ShapefileReader->getShapeType(Shapefile::FORMAT_INT));
            // PRJ
            $this->setPRJ($ShapefileReader->getPRJ());
            // Charset
            $this->setCharset($ShapefileReader->getCharset());
            // Bounding Box
            $this->overwriteComputedBoundingBox($ShapefileReader->getBoundingBox());
            // Fields
            foreach ($ShapefileReader->getFields() as $name => $field) {
                $this->addField($name, $field['type'], $field['size'], $field['decimals']);
            }
            // Next DBT available block
            if ($this->isFileOpen(Shapefile::FILE_DBT) && $this->getFileSize(Shapefile::FILE_DBT) > 0) {
                $this->dbt_next_available_block = ($this->getFileSize(Shapefile::FILE_DBT) / Shapefile::DBT_BLOCK_SIZE);
            }
            // Number of records
            $this->tot_records = $ShapefileReader->getTotRecords();
            // Close Shapefile
            $ShapefileReader = null;
            // Flag init headers
            $this->flag_init_headers = true;
            // SHP current offset (in 16-bit words)
            $this->shp_current_offset = $this->getFileSize(Shapefile::FILE_SHP) / 2;
            // Remove DBF EOF marker
            $dbf_size_without_eof = $this->getFileSize(Shapefile::FILE_DBF) - 1;
            $this->setFilePointer(Shapefile::FILE_DBF, $dbf_size_without_eof);
            if ($this->readData(Shapefile::FILE_DBF, 1) === $this->packChar(Shapefile::DBF_EOF_MARKER)) {
                $this->fileTruncate(Shapefile::FILE_DBF, $dbf_size_without_eof);
            }
            // Reset pointers
            foreach (array_keys($this->getFiles()) as $file_type) {
                $this->resetFilePointer($file_type);
            }
        }
    }
    
    /**
     * Destructor.
     *
     * Finalizes open files.
     * If files were NOT passed as stream resources, empty useless files will be removed.
     */
    public function __destruct()
    {
        // Flush buffers
        $this->writeBuffers();
        // Write DBF EOF marker to buffer
        $this->writeData(Shapefile::FILE_DBF, $this->packChar(Shapefile::DBF_EOF_MARKER));
        
        // Write headers only if Shapefile has been SUCCESSFULLY initialized (prevents execution when an Exception has been thrown during the first writeRecord() call)
        if ($this->isInitialized()) {
            // Set buffered file pointers to beginning of files
            foreach (array_keys($this->buffers) as $file_type) {
                $this->setFilePointer($file_type, 0);
            }
            // Write SHP, SHX, DBF and DBT headers to buffers
            $this->bufferData(Shapefile::FILE_SHP, $this->packSHPOrSHXHeader($this->getFileSize(Shapefile::FILE_SHP)));
            $this->bufferData(Shapefile::FILE_SHX, $this->packSHPOrSHXHeader($this->getFileSize(Shapefile::FILE_SHX)));
            $this->bufferData(Shapefile::FILE_DBF, $this->packDBFHeader());
            if ($this->dbt_next_available_block > 0) {
                $this->bufferData(Shapefile::FILE_DBT, $this->packDBTHeader());
            }
            // Write buffers containing the headers
            $this->writeBuffers();
            // Reset buffered file pointers
            foreach (array_keys($this->buffers) as $file_type) {
                $this->resetFilePointer($file_type);
            }
        }
        
        
        // Write PRJ file
        if ($this->isFileOpen(Shapefile::FILE_PRJ) && $this->getPRJ() !== null) {
            $this->writeData(Shapefile::FILE_PRJ, $this->packString($this->getPRJ()));
        }
        
        // Write CPG file
        if ($this->isFileOpen(Shapefile::FILE_CPG) && ($this->getCharset() !== Shapefile::DBF_DEFAULT_CHARSET || $this->getOption(Shapefile::OPTION_CPG_ENABLE_FOR_DEFAULT_CHARSET))) {
            $this->writeData(Shapefile::FILE_CPG, $this->packString($this->getCharset()));
        }
        
        // Close files and delete empty ones
        $this->closeFiles();
        if ($this->getOption(Shapefile::OPTION_DELETE_EMPTY_FILES)) {
            foreach ($this->getFilenames() as $filename) {
                if (filesize($filename) === 0) {
                    unlink($filename);
                }
            }
        }
    }<commit_msg>Corner case bug affecting ShapefileWriter destructor when no record has been written yet<commit_after><?php
/**
 * PHP Shapefile - PHP library to read and write ESRI Shapefiles, compatible with WKT and GeoJSON
 * 
 * @package Shapefile
 * @author  <NAME>
 * @version 3.0.2
 * @license MIT
 * @link    https://gasparesganga.com/labs/php-shapefile/
 */

namespace Shapefile;

class ShapefileWriter extends Shapefile
{
    /** SHP pack methods hash */
    private static $shp_pack_methods = [
        Shapefile::SHAPE_TYPE_NULL          => 'packNull',
        Shapefile::SHAPE_TYPE_POINT         => 'packPoint',
        Shapefile::SHAPE_TYPE_POLYLINE      => 'packPolyLine',
        Shapefile::SHAPE_TYPE_POLYGON       => 'packPolygon',
        Shapefile::SHAPE_TYPE_MULTIPOINT    => 'packMultiPoint',
        Shapefile::SHAPE_TYPE_POINTZ        => 'packPointZ',
        Shapefile::SHAPE_TYPE_POLYLINEZ     => 'packPolyLineZ',
        Shapefile::SHAPE_TYPE_POLYGONZ      => 'packPolygonZ',
        Shapefile::SHAPE_TYPE_MULTIPOINTZ   => 'packMultiPointZ',
        Shapefile::SHAPE_TYPE_POINTM        => 'packPointM',
        Shapefile::SHAPE_TYPE_POLYLINEM     => 'packPolyLineM',
        Shapefile::SHAPE_TYPE_POLYGONM      => 'packPolygonM',
        Shapefile::SHAPE_TYPE_MULTIPOINTM   => 'packMultiPointM',
    ];
    
    /** Buffered file types */
    private static $buffered_files = [
        Shapefile::FILE_SHP,
        Shapefile::FILE_SHX,
        Shapefile::FILE_DBF,
        Shapefile::FILE_DBT,
    ];
    
    
    /**
     * @var array   File writing buffers.
     */
    private $buffers = [];
    
    /**
     * @var integer Buffered records count.
     */
    private $buffered_record_count = 0;
     
    /**
     * @var integer Number of records.
     */
    private $tot_records = 0;
     
    /**
     * @var integer Current offset in SHP file and buffer (in 16-bit words).
     *              First 50 16-bit are reserved for file header.
     */
    private $shp_current_offset = 50; 
    
    /**
     * @var integer Next available block in DBT file.
     */
    private $dbt_next_available_block = 0;
    
    /**
     * @var bool    Flag representing whether file headers have been initialized or not.
     */
    private $flag_init_headers = false;
    
    
    
    /////////////////////////////// PUBLIC ///////////////////////////////
    /**
     * Constructor.
     * 
     * @param   string|array    $files      Path to SHP file / Array of paths / Array of handles of individual files.
     * @param   array           $options    Optional associative array of options.
     */
    public function __construct($files, $options = [])
    {
        // Options
        $this->initOptions([
            Shapefile::OPTION_BUFFERED_RECORDS,
            Shapefile::OPTION_CPG_ENABLE_FOR_DEFAULT_CHARSET,
            Shapefile::OPTION_DBF_FORCE_ALL_CAPS,
            Shapefile::OPTION_DBF_NULL_PADDING_CHAR,
            Shapefile::OPTION_DBF_NULLIFY_INVALID_DATES,
            Shapefile::OPTION_DELETE_EMPTY_FILES,
            Shapefile::OPTION_ENFORCE_GEOMETRY_DATA_STRUCTURE,
            Shapefile::OPTION_EXISTING_FILES_MODE,
            Shapefile::OPTION_SUPPRESS_M,
            Shapefile::OPTION_SUPPRESS_Z,
        ], $options);
        
        // Open files
        $this->openFiles($files, true);
        
        // Init Buffers
        $this->buffers = array_fill_keys(array_intersect(self::$buffered_files, array_keys($this->getFiles())), '');  
        
        // Mode overwrite
        if ($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_OVERWRITE) {
            foreach (array_keys($this->getFiles()) as $file_type) {
                if ($this->getFileSize($file_type) > 0) {
                    $this->fileTruncate($file_type);
                    $this->setFilePointer($file_type, 0);
                }
            }
        }
        
        // Mode append
        if ($this->getOption(Shapefile::OPTION_EXISTING_FILES_MODE) === Shapefile::MODE_APPEND && $this->getFileSize(Shapefile::FILE_SHP) > 0) {        
            // Open Shapefile
            $ShapefileReader = new ShapefileReader($this->getFiles(), [
                Shapefile::OPTION_DBF_CONVERT_TO_UTF8   => false,
                Shapefile::OPTION_DBF_FORCE_ALL_CAPS    => $this->getOption(Shapefile::OPTION_DBF_FORCE_ALL_CAPS),
                Shapefile::OPTION_DBF_IGNORED_FIELDS    => [],
                Shapefile::OPTION_IGNORE_SHAPEFILE_BBOX => false,
            ]);
            // Shape type
            $this->setShapeType($ShapefileReader->getShapeType(Shapefile::FORMAT_INT));
            // PRJ
            $this->setPRJ($ShapefileReader->getPRJ());
            // Charset
            $this->setCharset($ShapefileReader->getCharset());
            // Bounding Box
            $this->overwriteComputedBoundingBox($ShapefileReader->getBoundingBox());
            // Fields
            foreach ($ShapefileReader->getFields() as $name => $field) {
                $this->addField($name, $field['type'], $field['size'], $field['decimals']);
            }
            // Next DBT available block
            if ($this->isFileOpen(Shapefile::FILE_DBT) && $this->getFileSize(Shapefile::FILE_DBT) > 0) {
                $this->dbt_next_available_block = ($this->getFileSize(Shapefile::FILE_DBT) / Shapefile::DBT_BLOCK_SIZE);
            }
            // Number of records
            $this->tot_records = $ShapefileReader->getTotRecords();
            // Close Shapefile
            $ShapefileReader = null;
            // Flag init headers
            $this->flag_init_headers = true;
            // SHP current offset (in 16-bit words)
            $this->shp_current_offset = $this->getFileSize(Shapefile::FILE_SHP) / 2;
            // Remove DBF EOF marker
            $dbf_size_without_eof = $this->getFileSize(Shapefile::FILE_DBF) - 1;
            $this->setFilePointer(Shapefile::FILE_DBF, $dbf_size_without_eof);
            if ($this->readData(Shapefile::FILE_DBF, 1) === $this->packChar(Shapefile::DBF_EOF_MARKER)) {
                $this->fileTruncate(Shapefile::FILE_DBF, $dbf_size_without_eof);
            }
            // Reset pointers
            foreach (array_keys($this->getFiles()) as $file_type) {
                $this->resetFilePointer($file_type);
            }
        }
    }
    
    /**
     * Destructor.
     *
     * Finalizes open files.
     * If files were NOT passed as stream resources, empty useless files will be removed.
     */
    public function __destruct()
    {
        // Flush buffers
        $this->writeBuffers();
        // Write DBF EOF marker to buffer
        $this->writeData(Shapefile::FILE_DBF, $this->packChar(Shapefile::DBF_EOF_MARKER));
        
        // Try setting Shapefile as NULL SHAPE if it wasn't initialized yet (no record written)
        if (!$this->isInitialized()) {
            try {
                $this->setShapeType(Shapefile::SHAPE_TYPE_NULL);
            } catch (ShapefileException $e) {}
        }
        
        // Set buffered file pointers to beginning of files
        foreach (array_keys($this->buffers) as $file_type) {
            $this->setFilePointer($file_type, 0);
        }
        // Write SHP, SHX, DBF and DBT headers to buffers
        $this->bufferData(Shapefile::FILE_SHP, $this->packSHPOrSHXHeader($this->getFileSize(Shapefile::FILE_SHP)));
        $this->bufferData(Shapefile::FILE_SHX, $this->packSHPOrSHXHeader($this->getFileSize(Shapefile::FILE_SHX)));
        $this->bufferData(Shapefile::FILE_DBF, $this->packDBFHeader());
        if ($this->dbt_next_available_block > 0) {
            $this->bufferData(Shapefile::FILE_DBT, $this->packDBTHeader());
        }
        // Write buffers containing the headers
        $this->writeBuffers();
        // Reset buffered file pointers
        foreach (array_keys($this->buffers) as $file_type) {
            $this->resetFilePointer($file_type);
        }
        
        // Write PRJ file
        if ($this->isFileOpen(Shapefile::FILE_PRJ) && $this->getPRJ() !== null) {
            $this->writeData(Shapefile::FILE_PRJ, $this->packString($this->getPRJ()));
        }
        
        // Write CPG file
        if ($this->isFileOpen(Shapefile::FILE_CPG) && ($this->getCharset() !== Shapefile::DBF_DEFAULT_CHARSET || $this->getOption(Shapefile::OPTION_CPG_ENABLE_FOR_DEFAULT_CHARSET))) {
            $this->writeData(Shapefile::FILE_CPG, $this->packString($this->getCharset()));
        }
        
        // Close files and delete empty ones
        $this->closeFiles();
        if ($this->getOption(Shapefile::OPTION_DELETE_EMPTY_FILES)) {
            foreach ($this->getFilenames() as $filename) {
                if (filesize($filename) === 0) {
                    unlink($filename);
                }
            }
        }
    }
{'fraction_non_alphanumeric': 0.12706083001705515, 'fraction_numerical': 0.003069926094371802, 'mean_word_length': 2.131185475258099, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 196, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4648902', 'n_tokens_mistral': 5627, 'n_tokens_neox': 5197, 'n_words': 909}