Files
HotLunches/diagnose.summernote.php
2026-10-19 15:22:52 -07:00

339 lines
12 KiB
PHP

<?php
//
// diagnose.summernote.php
//
// READ-ONLY diagnostic. Runs SELECT statements only -- it makes no changes to the
// database or to any file. Delete this file once the output has been captured.
//
// Purpose: determine why the Summernote toolbar on the Page Content dialog has no
// Gallery button and no image upload. table-edit.php only includes summernote.images.php
// when at least one dialog field is typed 'editbox':
//
// table-edit.php:78 if ($ftype == 'editbox') $SummerNoteRequired = true;
// table-edit.php:106 if ($SummerNoteRequired) include "form.includes/summernote-header.php";
// table-edit.php:725 if ($SummerNoteRequired) include "summernote.images.php";
//
// Field types live in the `_form_fields` table, column `action_fields_screen_field_type`,
// keyed by `action_name` (see table-edit.functions.php:40).
//
// include/globals.php refuses to run unless the calling page declares this first
// (see include/globals.php:3-9). Same guard ajax.imageUploader.php uses.
$ValidAccess = 2010;
// Match how the app's own endpoints open the session (ajax.imageUploader.php),
// but do not restart one that is already active.
if (session_status() != PHP_SESSION_ACTIVE) {
$sessionName = 'hotlunch';
session_name($sessionName);
session_start();
}
require_once("include/globals.php");
if (SessionActive() != 1 || ! UserLoggedIn()) {
echo "Not logged in -- sign in to the admin site first, then reload this page.";
exit();
}
// Admin-only, same permission set the page content module uses.
$AllowedUserTypes = array('SuperUser','System Administrator','Application Administrator','Communications Administrator');
if (checkPermission($AllowedUserTypes) != 1) {
echo "Access denied.";
exit();
}
header("Content-Type: text/plain; charset=utf-8");
function out($label, $value = null) {
if ($value === null) echo("$label\n");
else echo(str_pad($label, 34) . $value . "\n");
}
function rule($ch = '-') {
echo(str_repeat($ch, 78) . "\n");
}
rule('=');
out("SUMMERNOTE / GALLERY DIAGNOSTIC");
out("generated", date("Y-m-d H:i:s"));
rule('=');
echo("\n");
//
// 1. Which actions exist that look like the page content screen.
//
out("[1] CANDIDATE ACTIONS");
rule();
$actionNames = array();
$query = "SELECT DISTINCT action_name FROM _form_fields ORDER BY action_name";
$res = db_query($query);
if ($res && db_num_rows($res)) {
while ($row = db_fetch_assoc($res)) {
$name = $row['action_name'];
// Flag anything that plausibly refers to page/content editing.
if (stripos($name, 'content') !== false || stripos($name, 'page') !== false) {
$actionNames[] = $name;
out(" match", $name);
}
}
}
if (count($actionNames) == 0) {
out(" (no action_name matched 'content' or 'page')");
$actionNames[] = 'page-content'; // fall back to the expected name
}
echo("\n");
//
// 2. Full field listing for each candidate action. This is the answer:
// the `content` row's screen_field_type must be 'editbox'.
//
out("[2] FIELD DEFINITIONS (_form_fields)");
rule();
foreach ($actionNames as $actionName) {
$safeName = db_escape_string($actionName);
echo("\n");
out("ACTION: $actionName");
rule();
printf(" %-28s %-16s %-8s %s\n", "column_name", "field_type", "seq", "prompt");
printf(" %-28s %-16s %-8s %s\n", "---------------------------", "---------------", "-------", "----------------");
$summernoteRequired = false;
$query = "SELECT * FROM _form_fields WHERE action_name = '$safeName' ORDER BY sequence";
$res = db_query($query);
if (!$res || !db_num_rows($res)) {
out(" (no rows)");
continue;
}
while ($row = db_fetch_assoc($res)) {
$col = $row['action_fields_column_name'];
$ftype = $row['action_fields_screen_field_type'];
$seq = array_key_exists('sequence', $row) ? $row['sequence'] : '';
$prompt = array_key_exists('action_fields_field_prompt', $row) ? $row['action_fields_field_prompt'] : '';
if ($ftype == 'editbox') $summernoteRequired = true;
// Mark the row that controls whether Summernote loads.
$flag = ($col == 'content') ? ' <<< ' : ' ';
printf("%s%-28s %-16s %-8s %s\n", $flag, $col, $ftype, $seq, $prompt);
}
echo("\n");
out(" \$SummerNoteRequired would be", $summernoteRequired ? "TRUE (summernote.images.php IS included)"
: "FALSE (summernote.images.php is NOT included)");
}
echo("\n");
//
// 3. What field types are actually in use across the whole app. Confirms that
// 'editbox' is a valid value here and shows how many screens rely on it.
//
out("[3] FIELD TYPES IN USE (whole _form_fields table)");
rule();
$query = "SELECT action_fields_screen_field_type AS ftype, COUNT(*) AS num " .
"FROM _form_fields GROUP BY action_fields_screen_field_type ORDER BY num DESC";
$res = db_query($query);
if ($res && db_num_rows($res)) {
while ($row = db_fetch_assoc($res)) {
out(" " . $row['ftype'], $row['num']);
}
}
echo("\n");
//
// 4. Every action that already uses an editbox. These are the screens where the
// Gallery currently works -- useful as a known-good comparison.
//
out("[4] ACTIONS ALREADY USING 'editbox'");
rule();
$query = "SELECT action_name, action_fields_column_name FROM _form_fields " .
"WHERE action_fields_screen_field_type = 'editbox' ORDER BY action_name";
$res = db_query($query);
if ($res && db_num_rows($res)) {
while ($row = db_fetch_assoc($res)) {
out(" " . $row['action_name'], $row['action_fields_column_name']);
}
} else {
out(" (none -- no screen in this database uses editbox)");
}
echo("\n");
//
// 5. The action record itself. table-edit.php:53 reads action_include_js_file from
// here and includes "form.includes/<that file>" at line 700. The browser shows a
// working Summernote editor whose textarea has NO 'summernote' class and where
// none of summernote.images.php's symbols exist -- so some other script is
// initializing it. This is where that script is named.
//
out("[5] ACTION RECORD (the _form_actions / action table row)");
rule();
$foundActionTable = false;
foreach (array('_form_actions','_actions','form_actions') as $tbl) {
if (!db_tableExists($tbl)) continue;
$foundActionTable = true;
out(" table", $tbl);
echo("\n");
$query = "SELECT * FROM $tbl WHERE action_name = 'page-content'";
$res = db_query($query);
if ($res && db_num_rows($res)) {
while ($row = db_fetch_assoc($res)) {
foreach ($row as $k => $v) {
// Print every column; the js/dialog/form file columns are what matter.
out(" $k", $v);
}
}
} else {
out(" (no row for action_name = 'page-content')");
}
break;
}
if (!$foundActionTable) out(" (could not locate the actions table)");
echo("\n");
//
// 6. Which form.includes files actually exist on this server, so we can see whether
// the js file named above is present.
//
out("[6] form.includes/ DIRECTORY ON THIS SERVER");
rule();
if (is_dir("form.includes")) {
$files = scandir("form.includes");
foreach ($files as $f) {
if ($f == '.' || $f == '..') continue;
out(" " . $f, filesize("form.includes/$f") . " bytes");
}
} else {
out(" (form.includes/ not found)");
}
echo("\n");
//
// 7. Confirm whether summernote.images.php is actually reachable from this directory.
// #image-gallery-dialog was absent in the browser even though $SummerNoteRequired
// is TRUE, so either the include path fails or something exits before line 725.
//
out("[7] FILE PRESENCE");
rule();
foreach (array("summernote.images.php",
"form.includes/summernote-header.php",
"table-edit.php",
"summernote/summernote-lite.min.js",
"load.dynamic.php") as $f) {
out(" $f", is_file($f) ? (filesize($f) . " bytes") : "*** MISSING ***");
}
echo("\n");
// Does the deployed summernote.images.php contain the gallery markup?
if (is_file("summernote.images.php")) {
$sn = file_get_contents("summernote.images.php");
out(" contains image-gallery-dialog", (strpos($sn, "image-gallery-dialog") !== false) ? "YES" : "NO");
out(" contains loadGallery", (strpos($sn, "loadGallery") !== false) ? "YES" : "NO");
out(" file size", strlen($sn) . " bytes");
}
echo("\n");
//
// 8. WHERE AM I? The uploaded file's byte count is not changing, which means the
// running app may be loading a different copy than the one being uploaded.
// Resolve the real paths so we can see exactly which directory is live.
//
out("[8] PATH RESOLUTION");
rule();
out(" this script __FILE__", __FILE__);
out(" this script dir", dirname(__FILE__));
out(" getcwd()", getcwd());
out(" DOCUMENT_ROOT", isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '(unset)');
out(" SCRIPT_FILENAME", isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '(unset)');
out(" REQUEST_URI", isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '(unset)');
out(" \$AppDir (from globals)", isset($AppDir) ? $AppDir : '(unset)');
echo("\n");
// The include in table-edit.php is relative ("summernote.images.php"), so PHP resolves
// it against the include_path and the including file's directory. Show both, plus
// every copy of the file that exists anywhere obvious.
out(" include_path", ini_get('include_path'));
echo("\n");
out(" FILES NAMED summernote.images.php");
$searchDirs = array(dirname(__FILE__), getcwd());
if (isset($AppDir)) $searchDirs[] = $AppDir;
if (isset($_SERVER['DOCUMENT_ROOT'])) $searchDirs[] = $_SERVER['DOCUMENT_ROOT'];
$seen = array();
foreach ($searchDirs as $d) {
$p = rtrim($d, '/\\') . "/summernote.images.php";
$real = @realpath($p);
if ($real === false) { out(" $p", "(not found)"); continue; }
if (array_key_exists($real, $seen)) continue;
$seen[$real] = true;
out(" $real", filesize($real) . " bytes md5=" . md5_file($real));
}
echo("\n");
// Same question for table-edit.php -- if the live one differs from the local copy,
// the include line numbers I have been reading do not match what actually runs.
out(" TABLE-EDIT.PHP ACTUALLY PRESENT HERE");
$te = dirname(__FILE__) . "/table-edit.php";
if (is_file($te)) {
out(" path", realpath($te));
out(" size", filesize($te) . " bytes");
out(" md5", md5_file($te));
out(" modified", date("Y-m-d H:i:s", filemtime($te)));
// Show the actual include line, whatever number it lives on.
$lines = file($te);
foreach ($lines as $n => $line) {
if (strpos($line, 'summernote.images.php') !== false) {
out(" include at line " . ($n + 1), trim($line));
}
if (strpos($line, 'BuildDialogForm(') !== false) {
out(" BuildDialogForm at line " . ($n + 1), trim($line));
}
}
} else {
out(" (table-edit.php not in this directory)");
}
echo("\n");
// And the deployed summernote.images.php: fingerprint it so we can tell at a glance
// whether an upload actually landed.
out(" DEPLOYED summernote.images.php FINGERPRINT");
$sn = dirname(__FILE__) . "/summernote.images.php";
if (is_file($sn)) {
out(" path", realpath($sn));
out(" size", filesize($sn) . " bytes");
out(" md5", md5_file($sn));
out(" modified", date("Y-m-d H:i:s", filemtime($sn)));
$c = file_get_contents($sn);
// Markers unique to my edited version -- if these are absent, the upload never landed.
out(" has 'noteTarget' (my edit)", (strpos($c, 'noteTarget') !== false) ? "YES" : "NO");
out(" has 'note-dialog-open' (my edit)", (strpos($c, 'note-dialog-open') !== false) ? "YES" : "NO");
out(" has '_allowInteraction' (my edit)", (strpos($c, '_allowInteraction') !== false) ? "YES" : "NO");
}
echo("\n");
rule('=');
out("END OF DIAGNOSTIC -- no changes were made.");
rule('=');
?>