Repository navigation
Expand file tree
/
Copy pathREADME
More file actions
403 lines (335 loc) · 22.9 KB
/
Copy pathREADME
File metadata and controls
403 lines (335 loc) · 22.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
Hotglue 1.9.0
developed by Gottfried Haider and Danja Vasiliev
and the people from WORM (worm.org)
with support from the Mondriaan Foundation
made for you
HOW DOES THIS WORK
* see INSTALL
* if you feel like reading boring lists, check out doc/FEATURES
* otherwise make sure you have seen http://www.vimeo.com/17924249
HOW TO INSTALL ADDITIONAL MODULES
* copy any module_*.php files to the main directory
* all auxiliary files go to the modules subdirectory
BUNDLED THIRD-PARTY CODE
* Moveable by Daybrush (MIT licensed)
* Alpine.js (MIT licensed)
* vanilla-picker by Andreas Borgen & Adam Brooks (ISC licensed)
* DejaVu Fonts (Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.)
* the file icon has been taken from gnome-icon-theme (GPL licensed)
RECENT CHANGES (2026-08)
* Pages can now be CENTERED. A page is either "infinite" - hotglue's
original unbounded canvas, still the default and unchanged for every
existing page - or "centered", where the content sits in a fixed-width
container that stays in the middle of the window and re-centers as the
window is resized. The button is in the page menu (double-click the
background); in centered mode two dashed handles at the container's
edges show the boundary and set its width by dragging.
Objects keep their exact coordinates either way: centering wraps them
in a container rather than moving them, so switching modes changes no
object file and nothing has to be migrated. Content positioned beyond
the container is not clipped or moved - it simply extends past it. The
page background still spans the full window; only the content is
centered.
* The color picker now opens next to the button that was clicked, and
above the object it is recoloring. It used to appear wherever the page
had last been scrolled to, could open off the top of the window, and
was painted over by the object being colored.
* Objects: a new button in the object context menu toggles whether content
bigger than the object's box is cut off or spills out of it. Objects
have always spilled, which stays the default - the setting is only
stored once you turn clipping on.
* Text objects: selecting text and clicking the link button in the text
context menu now opens a small dialog instead of a browser prompt. It
validates the URL (refusing javascript: and data:), adds https:// to a
bare domain while leaving anchors and page names alone, escapes the URL
into the href, optionally puts a class on the link, and can edit or
remove a link you already made.
* Text objects are now edited WYSIWYG: the markup stays hidden and a link
shows as underlined text, so editing looks like the published page. The
HTML source is still what gets stored - the </> button in the text menu
switches to editing it directly, which is also how to fix anything the
visual editor rewrites. Note the browser's HTML parser canonicalises
markup it round-trips: an unquoted attribute gains quotes and an
uppercase tag becomes lowercase, both of which render identically.
* Objects: the context menu's id button is now an Object Properties
dialog, showing the object as the <div> it renders as - id read-only,
hotglue's own classes fixed with a field for your own, and custom
attributes you can add. Attribute names are restricted and a denylist
(id, class, style, on*, contenteditable, draggable) is enforced both on
save and again when the page renders.
* Video module upgrade: uploaded videos (.mp4, .webm, .ogg/.ogv, .h264,
.mov) are now automatically re-encoded server-side via ffmpeg into a
single web-optimized variant, capped at 720p on the short side
(landscape or portrait, never upscaled) and trimmed to the first 60
seconds (VIDEO_MAX_HEIGHT/VIDEO_MAX_DURATION in config.inc.php), with a
poster frame generated automatically. Every upload is re-encoded
uniformly for predictable size/quality. Encoding happens in the
background; while pending, a lightweight "processing" placeholder is
shown instead of the raw original (so nothing large gets served before
it's ready), and the editor polls quietly and swaps in the finished
video without needing a page reload. Requires ffmpeg to be installed;
falls back to serving the original unmodified if it isn't
(VIDEO_ENCODING config constant). A dedicated "add video" icon was
added to the object-creation menu (previously videos could only be
added through the generic file-upload button).
* Removed the automatic page-snapshot/revision-history system
(check_auto_snapshot(), snapshot(), revert(), and the whole
revisions-browser UI) and the SNAPSHOT_MIN_AGE/SNAPSHOT_MAX_AGE config
constants. It only ran when the editor happened to be reopened for a
page, made no real undo guarantees, and Ctrl+Z previously did nothing
but suggest browsing it. Replaced with a proper, lightweight undo
stack in the editor: move, resize, z-order, create, delete, and any
property toggle that already saves through the normal object-save
path are now undoable via Ctrl+Z or a new undo icon in the
single-click menu (a few levels deep, purely in-browser-memory, reset
on page reload - not a replacement for backups). Attribute-only
changes that bypass the normal save path (e.g. page background/title,
a couple of per-module toggles) are not covered by this pass.
Existing leftover auto-* snapshot directories from before this change
are not cleaned up automatically anymore; run
`php tools/cleanup_auto_snapshots.php` (dry run by default, pass
--confirm to actually delete) if you want to reclaim that space.
* Raising a video's/large file's upload size limit is a php.ini setting,
not a Hotglue one - see KNOWN ISSUES below.
* Fixed: the undo stack above didn't capture deletions made via an
object's own context-menu delete icon (modules/object/object-edit.js) -
only the Delete key was wired up, so Ctrl+Z appeared to do nothing for
the more commonly-used delete button.
* Added redo (Ctrl+Y, or a new redo icon next to Undo in the single-click
menu) to the same undo stack - undoing and redoing can be freely mixed
and repeated in either direction. Performing a new action clears
whatever was available to redo, same as any standard undo/redo.
* Newly-uploaded images larger than IMAGE_UPLOAD_MAX_WIDTH/
IMAGE_UPLOAD_MAX_HEIGHT (default 512x512) now display at a capped size
on the canvas, in addition to the existing window-relative auto-shrink.
The original file and its recorded resolution are untouched - the
existing "reset image size" icon still restores the object to the
image's true full resolution.
* Image uploads now also accept .webp (image/webp), including server-side
resizing if the installed GD build supports it (IMAGE_WEBP_QUAL config
constant) - falls back gracefully (upload still works, just unresized)
if GD lacks webp support.
* Applied the same capped-display-size treatment to video
(VIDEO_DISPLAY_MAX_WIDTH/VIDEO_DISPLAY_MAX_HEIGHT, default 512x512,
independent of VIDEO_MAX_HEIGHT which caps the actual encoded
resolution) - "reset video size" restores the object to the encoded
video's real dimensions, same relationship as image's reset button.
* Fixed a real bug surfaced while adding the above: once a video finished
encoding, its corrected on-canvas size was written to disk, but an
already-open editor session kept showing the placeholder's leftover
default size (e.g. 480x360) until the page was reloaded. Two causes,
both fixed: video_render_object() finalized a pending encode too late
for the generic object-sizing hook to see the corrected dimensions
within that same render pass (module_video.inc.php), and video-edit.js's
completion poll only swapped the inner <video> tag, never the
container's own width/height style.
* Replaced the native <input type="color"> color picker with the vendored
vanilla-picker library (js/vanilla-picker.js), behind the same
$.glue.colorpicker API used by page-edit.js/text-edit.js. The native
picker's platform-dependent UI (a swatch grid on some Linux/Chromium
setups) made trying out shades require repeatedly reopening/confirming
rather than a continuous drag-to-preview.
* Removed the long-dead IE8_COMPAT config constant and its associated
fallback rendering code (module_image.inc.php, module_iframe.inc.php) -
Internet Explorer was already out of scope per the modern-evergreen
browser baseline. Updated INSTALL accordingly.
* Fixed same-site links/URLs (page links via "make the object a link",
links typed directly into rich text, the page browser listing, and
image/video/download/page-background-image URLs) being hard-coded to
whatever domain BASE_URL is configured/detected as, instead of staying
relative. This broke navigation and asset loading whenever a page was
accessed through a different domain than the configured one (e.g. a
custom domain pointed at the same Hotglue install) - links and embedded
media would silently jump back to the original domain. All of these are
now emitted as plain relative URLs, which resolve correctly against
whatever domain/path the page is actually being viewed on.
* Extended the same relative-URL fix to every static asset (all CSS/JS
file references, the favicon, @font-face URLs, and the error/create-page
templates' logo images and "take me home" link) via a single
centralized fix in html_add_css()/html_add_js()/html_favicon()
(html.inc.php) rather than touching each of the ~20 files that load
them, plus the client-side $.glue.base_url variable every module's
editor JS uses for its own icon/link URLs.
* Added a custom-class field to the "get id" context-menu icon (any
object): opens a small modal showing the object's id (read-only, for
linking to it) and an editable class field. The value is stored as
object-custom-class and appended alongside the object's own internal
classes when the page renders, so it's usable as a normal CSS/JS
selector without touching the object's real id.
* Fixed the XSRF referer check (controller.inc.php and json.php, guarding
every authenticated write action - saving, editing, uploads, creating
pages) comparing against the fixed base_url() instead of the domain the
request actually arrived on. Under a custom domain with BASE_URL
configured to the primary one, this rejected every legitimate editor
action as "possible xsrf" - likely the most disruptive of this whole
batch of custom-domain issues. Added request_base_url() (config.inc.php),
always derived from the current request, for this and similar checks to
use instead of base_url(). Also fixed the two login/create-page redirect
Location headers using the same fixed-domain pattern, and a latent bug
in the underlying host/port detection this surfaced (a non-standard
port could end up duplicated, e.g. "localhost:8931:8931").
* Auth hardening: HTTP Basic and Digest credential comparisons now use
hash_equals() instead of ==/!= (common.inc.php, util.inc.php), avoiding
timing side-channels. Added an optional AUTH_METHOD 'db', authenticating
against a UserCake-compatible accounts table via mysqli (DB_AUTH_HOST/
USER/PASSWORD/NAME/TABLE config constants, all empty/off by default) -
the authenticated account must match the site's own AUTH_USER, since one
database can back many sites. HTTP Digest's use of MD5 was left as-is:
that's the wire protocol itself (RFC 2617), not a code choice, and
there's no widely-supported alternative - treat 'digest' as legacy in
favor of 'basic' behind HTTPS if that's a concern.
* Fixed a hook-dispatch bug in image rendering, the same class of bug
fixed for video above: image_alter_render_early() backfilled
image-file-width/height (and default object-width/height) locally when
missing - e.g. images uploaded while GD was unavailable - but
invoke_hook() passes its $args array by value to every alter_render_early
hook, so object_alter_render_early() (which sets the container div's
CSS width/height, dispatched afterwards in the same pass) saw the
pre-backfill values on that first render. Moved the backfill
(_image_finalize_dimensions()) to the top of image_render_object(),
before the hook dispatch, mirroring video's fix.
* Added a click shield to video and webvideo (YouTube/Vimeo embed)
objects in edit mode: clicking one to select it or open its menu was
getting swallowed by the <video> tag's own playback/controls handling,
or - for webvideo - lost entirely inside the embed's cross-origin
<iframe> document, which can never bubble a click to the parent page at
all. A same-document overlay (glue-video-shield / glue-webvideo-shield)
covering the top 40% of the object intercepts the click instead,
transparent until hovered so it doesn't obscure the video, leaving the
rest of the object clickable so playback can still be tested while
editing. This replaces webvideo's old small corner drag handle
(glue-webvideo-handle) with the same, much larger shield.
* Added 90-degree object rotation: a new context-menu button next to the
existing flip button cycles 0/90/180/270deg in one click each,
composing a rotate(Ndeg) term into the same CSS transform string flip
already reads/writes (modules/transform/transform.js) - no PHP changes
needed. Known limitation: rotating and then flipping the same object
discards the rotation, since flip only recognizes its own 4 fixed
matrices - rotating after flipping works fine.
* Added site-wide settings, reachable at /pages (aliased /options) above
the existing page list:
- A site favicon, uploaded once and served at every page regardless
of which one is being viewed (stored as page-favicon-file/-mime on
the startpage's own page-object; served at the new /favicon route).
- Up to 10 custom fonts (.woff/.woff2/.ttf), listed with a hover-
revealed remove action (matching the existing page-list style).
Each uploaded font automatically becomes available in text objects'
"change typeface" button - it discovers typefaces by scanning the
page's CSS for .glue-font* rules, so uploading one just means
emitting a matching @font-face plus that companion rule
(module_text.inc.php's _include_custom_font()). Custom fonts sort
first in the cycle order, ahead of the 4 built-in WOFF fonts
(though still after the 6 plain system fonts, which live in a
separate static stylesheet that structurally can't be reordered
without moving them to generated CSS too).
- The "change typeface" button's tooltip is now just "<font name> |
add fonts ⚙", and the double-click ("page") menu's link to /pages
is now a plain gear glyph instead of a binary icon, matching the
undo/redo buttons' convention.
- The last font, font size, and line height picked via their
respective "change ..." controls are remembered site-wide and
applied as the default for newly created text objects, so you
don't have to dial them in again on every new one.
Found and fixed two real bugs along the way: module_page.inc.php was
missing require_once('controller.inc.php') (harmless via normal page
loads, which already pull it in first, but fatal-erroring the first
time the module loaded via json.php's upload endpoint instead); and the
favicon/font upload validation used && where it needed || (a non-empty-
but-unrecognized mime type slipped through unrejected) - then, once
fixed to ||, still wrongly rejected a valid file whenever the browser
reported an empty mime type (common for less-common font formats),
since the empty-mime branch never got a chance to run. Rewritten with
explicit $mime_ok/$ext_ok checks.
* Text editing improvements: default padding (12px 15px) and font-size
(18px) via a CSS class rule (modules/text/text.css, previously created
but never actually loaded) rather than inline styles, so the existing
"change padding"/"change font size" controls still reset to it
instantly with no reload; dragging font size now scales line-height
proportionally alongside it, preserving whatever ratio was already in
effect; the textarea's edit-mode focus indicator uses CSS outline
instead of border (border participates in the box model and shifts
text position by its own width even with box-sizing:border-box, outline
doesn't); and the selected-object border is now black instead of red.
* Added a "make link" button to text objects: select some text, click it,
enter a URL, and that selection gets wrapped in <a href="...">. No
rendering changes were needed for this - html_encode_str_smart()
(html_parse.inc.php) already passes well-formed HTML tags through
unescaped rather than encoding them, and text-edit.js's stop_editing()
already anticipated <a> tags inside text content (disabling their click
behavior while editing) - a hand-typed link already worked today, this
just gives an easier way to insert one. Also fixed: inline links inside
text content weren't visibly underlined, because of the global
`a { text-decoration: inherit; }` rule (css/main.css) that intentionally
keeps object-level links - e.g. a whole linked image - from forcing an
underline where there's no text to underline; added a scoped
`.text a { text-decoration: underline; }` override for inline links
specifically.
RECENT CHANGES (2026-07)
* jQuery and jQuery UI have been removed entirely from the editor
frontend, which is now plain vanilla JavaScript. Drag/resize is handled
by Moveable, a handful of reactive context-menu icons use Alpine.js, and
the color picker is a native <input type="color">. See MODERNIZATION.md
and handover.md for the full write-up of the migration.
* Fixed a bug in json.php where an unconditional stripslashes() call
(added for PHP8 compatibility) corrupted every saved object containing
escaped quotes.
* Fixed a bug in module_download.inc.php that called an undefined
log_error() function, causing an error when deleting a page containing
a download object.
* Fixed a module-name typo in webvideo-edit.js's register_alter_pre_save()
call (was registered under 'iframe' instead of 'webvideo').
* Fixed a misplaced parenthesis in module_image.inc.php's image resize
comparison.
* Added a feature: holding shift while dragging an object's resize handle
now preserves its aspect ratio.
* Fixed images looking blurry after being resized in the editor. The
resize was requested at CSS pixel dimensions rather than device pixel
dimensions, so on any HiDPI/Retina display (devicePixelRatio 2 or 3 -
most phones, laptops and monitors today) the browser stretched a
same-resolution image to fill a box rendered at 2-3x that many
physical pixels. Now capped/scaled via the new IMAGE_RESIZE_MAX_DPR
config constant (default 2).
* Restyled the object resize handles: a plain square with a black
outline, a light grey semi-transparent backdrop, and a smaller white
square centered inside, matching the look of the toolbar/context-menu
icons instead of Moveable's default blue circles.
* PHP 8+ modernization pass: error_reporting no longer references the
long-deprecated E_STRICT constant; removed dead PHP<7.1 compatibility
code (an is_iterable() polyfill and version_compare() branches that
were always-true on any supported PHP 8.x); modernized the PHPUnit
test suite's deprecated @expectedException annotation; mechanically
converted array(...) to [...] throughout the codebase; fixed a null-
safety bug in save_state() where a malformed save request with no id
attribute could propagate null into several string functions; replaced
32 sites of @is_array()/@is_string()/@is_numeric() error-suppression
with explicit isset() checks.
* Removed a handful of orphaned files no longer referenced anywhere:
the Farbtastic color-picker icons (dead since the color picker was
replaced with a native <input type="color">), two unused text-object
icons, and a stale unminified-only welcome.min.js.
KNOWN ISSUES
* Hotglue 1.0 is not compatible with prior versions of the software. We aim
to preserve compatibility with upcoming releases though.
* On certain shared hosts we have seen issues with digest authentication (see AUTH_METHOD configuration option). For those hosts basic authentication has to be used.
* On certain hosts or configurations it might be required to explicitly set RewriteBase in .htaccess when using short URLs. This has been observed on hosts using mod_rewrite even before the request hits the Hotglue directory. If you experience unexpected blank pages after installation or can't manage to log in, this might be worth trying.
* On certain (cheap) hosts the use of .htaccess file is disallowed. If you are experiencing strange errors like ERROR 500 try renaming .htaccess file back to htaccess-dist and see if that helps. Sometimes you might be able to request .htaccess functionality from your webhosting provider.
* Native JSON decoding seems to be broken in early versions of PHP 5.2. This can result in error messages such as "Error saving state as class 'object' is not set". Make sure you are using PHP 5.2.6 or later.
* Support for supporting hosts that have PHP installed as a (Fast-)CGI binary is still worked on. For now it is recommended to use mod_php, although success with FastCGI has been reported as well.
* Rewrite rules in the Apache configuration or in any parent directory .htaccess file can clash with the Hotglue one.
* If you receive an error message while uploading a large file this
might be due to the limits set in the servers's php.ini file.
You might want to change the values of these settings:
- post_max_size
- upload_max_filesize
- memory-limit (if memory limits have been enabled)
- max_input_time
- max_file_uploads
When running via the built-in PHP dev server (php -S), these can be
overridden per-invocation without touching php.ini, e.g.:
php -S localhost:8000 -d upload_max_filesize=200M -d post_max_size=220M
* For now, caching or seeking is not implemented for retrieval of uploaded files.
This might be an issue when embedding large video files.
* Making animated GIF images smaller than their native size disables the animation.
* The looping property of YouTube's new HTML5 video embed code seems to be broken at the moment.
* Vimeo does not currently offer a way to embed videos over HTTPS. You might thus receive a (legitimate) browser warning when accessing a page containing a Vimeo video using HTTPS.
* When modifying objects that also appear on other pages, these changes might not immediately appear on them if caching is enabled.