Skip to content
← Back to Functions
Code

Broken Asset Finder

Scans templates/content for asset references and reports missing local files.

Function signature

ogFindBrokenAssetReferences(base_path, documents = array(), options = array())

Categories

  • File and Upload Safety

Parameters

base_pathApproved root directory used to keep path operations contained.documentsStructured documents data supplied by the caller.optionsOptional policy, formatting, or behavior controls for this helper.

Return value

Public-safe status string returned by the function.

  • 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 procedural mysqli prepared execution where SQL plans are returned; validate file paths, MIME policies, and permissions before file or download workflows.

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.
 */

/**
 * Scans templates/content for asset references and reports missing local files.
 *
 * Primary use case: Deployment audits.
 * Typical inputs: file paths, document contents.
 * Typical output: missing asset report.
 *
 * Implementation note: Do not fetch remote URLs in this helper.
 *
 * @return array Structured result data with success, message, and data keys.
 */
function ogFindBrokenAssetReferences($base_path, $documents = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	$base_path = (string)$base_path;
	if (!is_array($documents)) {
		$result['message'] = 'Documents must be an array.';
		return $result;
	}
	if (!is_array($options)) {
		$options = array();
	}

	$base_real = realpath($base_path);
	if ($base_real === false || !is_dir($base_real)) {
		$result['message'] = 'Asset base path is invalid.';
		return $result;
	}

	$missing = array();
	$found = array();
	$references = array();
	foreach ($documents as $document_name => $content) {
		$content = (string)$content;
		$matches = array();
		preg_match_all('/(?:src|href)=([\"\'])([^\"\']+)\1|url\(([\"\']?)([^\)\"\']+)\3\)/i', $content, $matches, PREG_SET_ORDER);

		foreach ($matches as $match) {
			$reference = '';
			if (!empty($match[2])) {
				$reference = trim($match[2]);
			} elseif (!empty($match[4])) {
				$reference = trim($match[4]);
			}
			if (empty($reference)) {
				continue;
			}
			if (preg_match('/^(https?:)?\/\//i', $reference) || strpos($reference, 'data:') === 0 || strpos($reference, '#') === 0) {
				continue;
			}

			$clean_reference = strtok($reference, '?');
			$clean_reference = strtok($clean_reference, '#');
			$clean_reference = '/' . ltrim($clean_reference, '/');
			$references[] = $clean_reference;
			$local_path = $base_real . str_replace('/', DIRECTORY_SEPARATOR, $clean_reference);
			$real_path = realpath($local_path);
			if ($real_path === false || strpos($real_path, $base_real) !== 0 || !is_file($real_path)) {
				$missing[] = array('document' => (string)$document_name, 'reference' => $clean_reference);
			} else {
				$found[] = $clean_reference;
			}
		}
	}

	$result['success'] = true;
	$result['message'] = 'Asset reference scan completed.';
	$result['data'] = array(
		'reference_count' => count($references),
		'found_count' => count(array_unique($found)),
		'missing_count' => count($missing),
		'missing' => $missing
	);

	return $result;
}