Skip to content
← Back to Functions
Code

Safe File Previewer

Creates a safe preview for text/CSV/JSON files without rendering unsafe HTML.

Function signature

ogBuildSafeFilePreview(base_path, requested_path, options = array())

Categories

  • File and Upload Safety

Parameters

base_pathApproved root directory used to contain all local file operations.requested_pathCaller-supplied relative path to resolve safely under the base directory.optionsOptional documented policy controls for the helper.

Return value

Public-safe status string returned by the function for controller branching or logging.

  • success
  • message
  • data

Compatibility

Existing function name, slug, path, and call order preserved; advertised metadata corrected to the actual source behavior.

Minimum PHP version: 7.4

Security notes

Use caller-owned allowlists and context-specific escaping; validate file paths, routes, email tokens, cart totals, discount rules, and tax-region rules before production use.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Creates a safe preview for text/CSV/JSON files without rendering unsafe HTML.
 *
 * Primary use case: Admin import previews.
 * Typical inputs: base path, file path, type, max bytes.
 * Typical output: preview data.
 *
 * Implementation note: Escape preview output and cap bytes.
 *
 * @return array Structured result data with success, message, and data keys.
 */
function ogBuildSafeFilePreview($base_path, $requested_path, $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (!is_array($options)) {
		$options = array();
	}

	$safe_path = ogResolveSafePath($base_path, $requested_path, array('must_exist' => true));
	if (empty($safe_path['success'])) {
		$result['message'] = $safe_path['message'];
		return $result;
	}

	$file_path = $safe_path['data']['safe_path'];
	if (!is_file($file_path) || is_link($file_path)) {
		$result['message'] = 'Preview target must be a regular file.';
		return $result;
	}

	$max_bytes = 65536;
	if (!empty($options['max_bytes'])) {
		$max_bytes = (int)$options['max_bytes'];
	}
	if ($max_bytes < 1024) {
		$max_bytes = 1024;
	}

	$handle = fopen($file_path, 'rb');
	if (empty($handle)) {
		$result['message'] = 'Preview file could not be opened.';
		return $result;
	}

	$contents = fread($handle, $max_bytes + 1);
	fclose($handle);
	if ($contents === false) {
		$result['message'] = 'Preview file could not be read.';
		return $result;
	}

	$truncated = false;
	if (strlen($contents) > $max_bytes) {
		$contents = substr($contents, 0, $max_bytes);
		$truncated = true;
	}

	$type = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
	$parsed = array();
	if ($type == 'json') {
		$decoded = json_decode($contents, true);
		if (json_last_error() == JSON_ERROR_NONE) {
			$parsed = $decoded;
		}
	} elseif ($type == 'csv') {
		$lines = preg_split('/\r\n|\r|\n/', $contents);
		$limit = 5;
		$count = 0;
		foreach ($lines as $line) {
			if ($count >= $limit) {
				break;
			}
			$parsed[] = str_getcsv($line);
			$count++;
		}
	}

	$result['success'] = true;
	$result['message'] = 'Safe file preview built.';
	$result['data'] = array(
		'path' => $safe_path['data']['requested_path'],
		'extension' => $type,
		'bytes_read' => strlen($contents),
		'truncated' => $truncated,
		'plain_text' => $contents,
		'html_preview' => htmlspecialchars($contents, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
		'parsed_preview' => $parsed
	);

	return $result;
}