Skip to content

menj/citepress

Repository files navigation

CitePress

WordPress PHP License Version

WordPress plugin for academic citation management. Provides 20 citation formats, 6 export formats, bibliography and inline citation shortcodes, JSON-LD/Scholar/OG structured data, GDPR-compliant analytics, and a Gutenberg dynamic block.


Requirements

Minimum Recommended
WordPress 5.0 6.4+
PHP 7.2 8.0+
MySQL 5.6 8.0+

No Composer dependencies. No npm build step. Drop-in compatible.


Installation

# From WordPress admin
Plugins → Add New → Upload → citepress-2.9.13.zip

# Manual
unzip citepress-2.9.13.zip -d /var/www/html/wp-content/plugins/
wp plugin activate citepress

On activation, register_activation_hook calls citepress_create_analytics_table() which runs dbDelta() to create the {prefix}citepress_analytics table. The table is only created if analytics are enabled and the table does not already exist.


Directory Structure

citepress/
│
├── citepress.php               Bootstrap. Defines constants, loads defaults, sets globals,
│                               registers activation/deactivation hooks, requires includes.
│
├── uninstall.php               Runs on plugin deletion. Drops analytics table, removes
│                               all citepress_* options, clears the daily purge cron event.
│
├── includes/
│   ├── analytics.php           Database setup and upgrade (dbDelta). GDPR personal data
│   │                           exporter and eraser. Daily purge cron. Manual purge handler.
│   │                           AJAX tracking endpoint (logged-in and non-logged-in).
│   │                           Consent detection from cookie values.
│   │
│   ├── settings.php            Registers the citepress_setting option group. Sanitization
│   │                           callback. Admin menu pages (Settings and Analytics).
│   │                           Settings page HTML with tabbed UI (General, Formats, Display,
│   │                           Metadata & SEO, Preview). Analytics page HTML with stats.
│   │
│   ├── shortcodes.php          All citation logic. Author resolution (WordPress user,
│   │                           guest-author meta, Co-Authors Plus). Citation style
│   │                           definitions (20 formats). Inline citation templates.
│   │                           Bibliography mode detection. [cite] and [cite_bibliography]
│   │                           shortcode handlers and their namespaced aliases.
│   │                           Wikipedia citation builder.
│   │
│   ├── metadata.php            Auto-display hook on the_content. SEO plugin conflict guard.
│   │                           Google Scholar <meta> tag output. JSON-LD ScholarlyArticle
│   │                           structured data. Open Graph academic article tags.
│   │
│   └── assets.php              Frontend CSS/JS enqueue. Per-page citepressSharedData and
│                               citepressCitationData inline JSON output (wp_footer).
│                               Admin CSS/JS enqueue (Settings and Analytics pages only).
│                               Block editor asset enqueue. Gutenberg block registration
│                               and server-side render callback.
│
├── assets/
│   ├── css/
│   │   ├── frontend.css        Citation box, inline citation, and bibliography styles.
│   │   │                       Supports light/dark mode via data-theme attribute and
│   │   │                       prefers-color-scheme media query.
│   │   ├── admin.css           Settings page, Analytics page, and Preview tab styles.
│   │   └── block-editor.css    Block editor sidebar and citation box preview styles.
│   │
│   └── js/
│       ├── frontend.js         Citation rendering, format switching, copy-to-clipboard,
│       │                       export file generation, analytics AJAX dispatch,
│       │                       and consent detection logic.
│       ├── admin.js            Settings tab switching (with active-tab persistence),
│       │                       radio card selection, live preview rendering,
│       │                       and analytics purge confirm dialog.
│       └── block.js            Gutenberg block edit component. Reads citepressBlockData
│                               global. Renders citation preview in block editor sidebar.
│
├── languages/
│   └── cite.pot                POT file for translation. Text domain: citepress.
│
├── CHANGELOG.md                Full technical changelog (developer audience).
├── UPGRADING.md                Post-release issue log, root cause analysis, upgrade notes.
└── readme.txt                  WordPress.org readme (general/layman audience).

Include file boundary rule: Each file in includes/ owns a clearly defined responsibility. A single function defined in two include files causes a PHP fatal on every page load. Before releasing, run:

grep -rh "^function citepress_" includes/ | sort | uniq -d

Any output means a duplicate — fix it before packaging.


Constants

Defined in citepress.php:

Constant Value Purpose
CITEPRESS_VERSION '3.0.0' Cache-busting for enqueued assets
CITEPRESS_PLUGIN_DIR plugin_dir_path(__FILE__) Absolute filesystem path
CITEPRESS_PLUGIN_URL plugin_dir_url(__FILE__) Full URL to plugin root
CITEPRESS_ASSETS_URL CITEPRESS_PLUGIN_URL . 'assets/' Full URL to assets directory
CITEPRESS_DB_VERSION '1.1' Analytics table schema version

DB version: Bump CITEPRESS_DB_VERSION in citepress.php whenever the analytics table schema changes. citepress_maybe_upgrade_db() runs on admin_init and calls dbDelta() automatically when the stored citepress_db_version option differs from the constant.


Global Variables

Set in citepress.php at plugin load time:

Global Type Purpose
$citepress_setting array Merged user settings + defaults. Available everywhere via global $citepress_setting.
$citepress_citation_data array Accumulates per-instance citation data during shortcode rendering. Serialised to citepressCitationData in wp_footer.
$citepress_cited_posts array Tracks which post IDs have been cited (for bibliography deduplication).
$citepress_ref_counter int Auto-incrementing footnote reference number for bibliography mode.
$citepress_bibliography_mode bool Set to true when [cite_bibliography] is present on the page. Makes [cite] render as superscript markers instead of full boxes.
$citepress_instance_counter int Unique ID for each citation box instance on the page.

Settings

Stored as a single serialised array in wp_options under the key citepress_setting. Option group: citepress_setting. All values pass through citepress_sanitize_settings().

Default values (filterable via citepress_default_setting):

Key Default Description
auto_display 'no' Append citation box to post content automatically
display_position 'bottom' 'top' or 'bottom' for auto-display
show_toggle 'yes' Show collapse/expand toggle on citation box
enable_analytics 'no' Enable analytics tracking
require_consent_for_analytics 'yes' Gate analytics on cookie consent
analytics_cooldown_mode 'session' 'session', 'ip_hash', or 'none'
analytics_retention_days '90' Days before analytics rows are auto-purged
post_types ['post'] Post types to auto-display on
excluded_posts '' Comma-separated post IDs to skip
show_google_scholar 'yes' Output Scholar <meta> tags
show_jsonld 'yes' Output JSON-LD structured data
show_og_academic 'yes' Output Open Graph academic tags
et_al_threshold '3' Author count before "et al." kicks in
format_display_mode 'dropdown' 'dropdown', 'tabs', or 'buttons'
color_scheme 'auto' 'auto', 'light', or 'dark'
enabled_export_formats all 6 Which export buttons to show
enabled_formats all 20 Which citation styles are available

Hooks Reference

Actions

Hook Callback Priority File
admin_init citepress_register_setting 10 settings.php
admin_init citepress_maybe_upgrade_db 10 analytics.php
admin_init citepress_handle_analytics_purge 10 analytics.php
admin_init citepress_add_privacy_policy_content 10 analytics.php
admin_menu citepress_setting_menu 10 settings.php
admin_enqueue_scripts citepress_enqueue_admin_assets 10 assets.php
wp citepress_schedule_purge 10 analytics.php
wp_enqueue_scripts citepress_enqueue_frontend_styles 10 assets.php
wp_enqueue_scripts citepress_enqueue_frontend_scripts 10 assets.php
wp_footer citepress_output_citation_data 10 assets.php
wp_head citepress_add_google_scholar_tags 5 metadata.php
wp_head citepress_add_jsonld_structured_data 10 metadata.php
wp_head citepress_add_og_academic_tags 5 metadata.php
wp_ajax_citepress_track_analytics citepress_track_analytics 10 analytics.php
wp_ajax_nopriv_citepress_track_analytics citepress_track_analytics 10 analytics.php
init citepress_register_block_type 10 assets.php
enqueue_block_editor_assets citepress_enqueue_block_editor_assets 10 assets.php
citepress_daily_purge_event citepress_do_daily_purge 10 analytics.php
register_activation_hook citepress_create_analytics_table citepress.php
register_deactivation_hook citepress_deactivate citepress.php

Filters

These are the apply_filters() call sites — where you can hook in to modify behaviour.

Citation output:

Filter Args Description
citepress_citation_styles $styles (array of 20 format definitions) Add, remove, or modify citation style templates. Memoised — fires once per request.
citepress_inline_templates $templates (array, one per format) Modify the parenthetical inline citation templates.
citepress_inline_template $template, $style Modify a single inline template for a specific format at render time.
citepress_inline_citation $html, $atts, $post Filter the complete rendered inline citation HTML before output.
citepress_bibliography_heading $heading (string) Modify the bibliography section heading text.
citepress_bibliography_entry $entry_html, $ref_number, $entry Modify a single bibliography list item before it is appended.
citepress_bibliography_output $output, $unique_entries Filter the complete bibliography HTML before output.

Structured data:

Filter Args Description
citepress_scholar_meta $meta_data (array) Modify the Scholar <meta> tag data array before output.
citepress_jsonld_data $jsonld, $post Modify the JSON-LD structured data array before json_encode.
citepress_og_academic_data $og_data (array) Modify the Open Graph academic tag data array before output.
citepress_output_scholar_meta false Return true to force Scholar meta output even when a supported SEO plugin is active.
citepress_output_jsonld false Return true to force JSON-LD output even when a supported SEO plugin is active.
citepress_output_og_tags false Return true to force OG tag output even when a supported SEO plugin is active.

Analytics and GDPR:

Filter Args Description
citepress_analytics_optout_active false Return true to suppress analytics tracking for the current request (e.g. for admins or logged-in users).
citepress_analytics_consent_granted null Return true to grant consent programmatically, false to deny it. Return null to fall through to the cookie-detection logic.
citepress_analytics_identity_resolver null, $email_address Map an email address to an array ['post_ids' => [...]] for GDPR data export and erase. Return null if the email cannot be mapped (default — plugin uses session-based tracking with no PII).

Settings:

Filter Args Description
citepress_default_setting $defaults (array) Modify the default settings array before it is merged with stored options. Use this to change defaults for new installs or to force-override specific settings.

Shortcodes

[cite] / [citepress_cite]

Renders a citation box or inline citation.

Attribute Type Default Description
style string 'apa' Default citation format to show on load
mode string 'box' 'box', 'inline', or 'bibliography'
formats string Comma-separated list of format IDs to include (overrides enabled formats)
exclude_formats string Comma-separated format IDs to hide for this instance
show_copy string 'true' 'true' or 'false'
show_export string 'true' 'true' or 'false'
show_toggle string 'true' 'true' or 'false'
custom_author string Override the post author name
page int Page number appended in inline mode
link string 'true' 'true' or 'false' — include a link in inline mode

[cite_bibliography] / [citepress_cite_bibliography]

Renders a numbered reference list. When this shortcode is detected anywhere on the page, all [cite] shortcodes on that page switch into bibliography mode and render as superscript footnote markers [1], [2], etc. instead of full citation boxes.

Attribute Type Default Description
heading string 'References' Section heading text. Filterable via citepress_bibliography_heading.
style string 'apa' Citation format for the bibliography list entries

Analytics AJAX Endpoint

Action: citepress_track_analytics Methods: POST (both wp_ajax_* and wp_ajax_nopriv_* — no nonce required by design; nonces break full-page caching)

POST fields:

Field Type Description
post_id int Post being cited
citation_style string Format ID (e.g. 'apa')
action_type string 'view', 'copy', or 'export'

Server-side consent logic (citepress_track_analytics()):

  1. Check citepress_analytics_optout_active filter — if true, bail.
  2. Check citepress_analytics_consent_granted filter — if false, bail; if true, proceed; if null, fall through.
  3. Call citepress_detect_consent_from_cookies() — inspects cookies set by supported consent plugins (Borlabs Cookie, Cookiebot, GDPR Cookie Consent, Cookie Notice). If a known plugin is present and has explicitly denied consent, bail. If no known plugin is detected, allow (nothing to check against).
  4. Apply cooldown: session-based (transient keyed to PHPSESSID), IP-hash-based, or none.
  5. Insert row into {prefix}citepress_analytics and invalidate the stats cache.

JavaScript consent logic (hasAnalyticsConsent() in frontend.js):

Performs the same cookie checks client-side as a pre-flight before dispatching the AJAX call. The PHP side makes the authoritative decision — it can read HttpOnly cookies that JS cannot.


Database

Table: {prefix}citepress_analytics

Created by citepress_create_analytics_table() on plugin activation and kept current by citepress_maybe_upgrade_db() on admin_init.

Column Type Description
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT Primary key
post_id bigint(20) UNSIGNED NOT NULL WP post ID
citation_style varchar(50) NOT NULL Format ID string
action_type varchar(20) NOT NULL 'view', 'copy', or 'export'
timestamp datetime DEFAULT CURRENT_TIMESTAMP UTC insert time

Indexes: PRIMARY KEY (id), KEY post_id (post_id), KEY action_type (action_type), KEY timestamp (timestamp).

Schema version: Controlled by CITEPRESS_DB_VERSION constant. To add a column: update the CREATE TABLE statement in citepress_create_analytics_table(), bump the constant, and citepress_maybe_upgrade_db() will run dbDelta() on the next admin_init.


JavaScript Globals

Output in wp_footer by citepress_output_citation_data() in assets.php. Only present on pages that contain at least one citation box.

window.citepressSharedData

Output once per page. Contains data shared across all citation boxes.

{
  styles: { apa: { name, template, ... }, mla: { ... }, ... }, // all 20 formats
  ajaxUrl: "https://example.com/wp-admin/admin-ajax.php",
  enableAnalytics: true,
  requireConsent: true
}

window.citepressCitationData

Array of per-instance objects, one per [cite] shortcode on the page.

[
  {
    instanceId: "citepress-1",
    postId: 42,
    author: "Smith, J.",
    title: "Article Title",
    year: "2024",
    url: "https://...",
    accessDate: "11 March 2026",
    formats: ["apa", "mla", "chicago"],   // enabled for this instance
    defaultStyle: "apa",
    showCopy: true,
    showExport: true,
    showToggle: true,
    colorScheme: "auto",
    exportFormats: ["bibtex", "ris", ...]
  },
  ...
]

window.citepressBlockData

Output by citepress_enqueue_block_editor_assets(). Available in the block editor only.

{
  styles: { ... },      // same as citepressSharedData.styles
  defaultStyle: "apa",
  postData: { author, title, year, url }
}

Enqueued Assets

Handle File Hook Condition
citepress-frontend assets/css/frontend.css wp_enqueue_scripts All front-end pages
citepress-frontend assets/js/frontend.js wp_enqueue_scripts All front-end pages
citepress-admin assets/css/admin.css admin_enqueue_scripts Settings and Analytics pages only
citepress-admin assets/js/admin.js admin_enqueue_scripts Settings and Analytics pages only
citepress-block assets/js/block.js enqueue_block_editor_assets Block editor only
citepress-block-editor assets/css/block-editor.css enqueue_block_editor_assets Block editor only

Admin assets are guarded by hook suffix check:

if ( 'toplevel_page_citepress' !== $hook && 'citepress_page_citepress-analytics' !== $hook ) {
    return;
}

Gutenberg Block

Block name: citepress/block Registration: register_block_type() in citepress_register_block_type() on init Render type: Dynamic — save() returns null; output is generated server-side by citepress_render_citation_block( $attributes ) Sidebar controls: Default citation style, format display mode (dropdown/tabs/buttons), which formats to show, show/hide copy and export buttons

The block calls citepress_display_citation() server-side with the block attributes merged over the page-level shortcode defaults.


SEO Plugin Conflict Guard

citepress_seo_plugin_active() in metadata.php returns true if any of these plugins is active:

  • Yoast SEO (wpseo-general option)
  • RankMath (rank_math_modules option)
  • All in One SEO (aioseo_options option)
  • The SEO Framework (autodescription-site-settings option)

When active, Scholar meta, JSON-LD, and OG tags are suppressed by default to avoid duplicate structured data. Each can be re-enabled individually via its corresponding filter:

add_filter( 'citepress_output_jsonld', '__return_true' );
add_filter( 'citepress_output_scholar_meta', '__return_true' );
add_filter( 'citepress_output_og_tags', '__return_true' );

Custom Fields

Field key Description
guest-author Overrides the WordPress post author name in citations. Takes precedence over Co-Authors Plus.
orcid Author ORCID identifier. Included in citation output where the format supports it and in Scholar meta tags.

GDPR / Privacy Tools

CitePress registers a personal data exporter and eraser with WordPress privacy tools (wp_privacy_personal_data_exporters / wp_privacy_personal_data_erasers).

By default these tools do nothing because session-based tracking stores no PII. To wire them up for a custom identity resolver (e.g. if you extend the plugin to associate analytics with user accounts), implement the citepress_analytics_identity_resolver filter:

add_filter( 'citepress_analytics_identity_resolver', function( $identifier, $email ) {
    $user = get_user_by( 'email', $email );
    if ( ! $user ) return null;
    $post_ids = get_posts([
        'author'      => $user->ID,
        'post_status' => 'any',
        'fields'      => 'ids',
        'numberposts' => -1,
    ]);
    return $post_ids ? [ 'post_ids' => $post_ids ] : null;
}, 10, 2 );

Adding a Custom Citation Format

add_filter( 'citepress_citation_styles', function( $styles ) {
    $styles['my_format'] = [
        'name'     => 'My Custom Format',
        'template' => '{author} ({year}). <em>{title}</em>. {url}.',
    ];
    return $styles;
} );

Format IDs must be lowercase alphanumeric with underscores. The template tokens available are: {author}, {year}, {title}, {url}, {access_date}, {publisher}, {journal}, {volume}, {issue}, {pages}, {doi}, {orcid}.


Hosting Notes

Object caching: Analytics stats queries use wp_cache_get / wp_cache_set with the citepress cache group. On object-cache-enabled hosts (Redis, Memcached), the cache is invalidated on every successful analytics insert and on manual purge. No action needed — WP's cache API handles this transparently.

Full-page caching: The analytics AJAX endpoint (admin-ajax.php) must not be cached. Most caching plugins exclude admin-ajax.php by default. The analytics endpoint intentionally has no nonce — nonces contain a timestamp and change every 12 hours, which would break cached pages that embed a nonce in the markup.

Cron: citepress_schedule_purge() registers a daily WP-Cron event (citepress_daily_purge_event) that deletes analytics rows older than the configured retention period. On hosts with WP-Cron disabled (DISABLE_WP_CRON = true), set up a real cron job:

*/30 * * * * php /var/www/html/wp-cron.php > /dev/null 2>&1

Database: The plugin creates one table. No custom indexes beyond what is listed in the schema section above. Queries use $wpdb->prepare() for all user-supplied values. Integer IDs from absint() are inlined directly.

Multisite: Not tested in multisite. The analytics table is created in the active site's database. Settings are stored per-site. No network admin UI.


Security

Threat Mitigation
XSS All output goes through esc_html(), esc_attr(), esc_url(), or wp_kses()
SQL injection $wpdb->prepare() for all parameterised queries; integer IDs inlined after absint()
CSRF Settings form uses settings_fields() (WP nonce). Manual purge uses a dedicated nonce. Analytics AJAX has no nonce (intentional — see caching note above)
Privilege escalation Settings and analytics pages require manage_options. Purge action verifies nonce and capability before acting
Rate limiting Analytics tracking uses transients (session or IP-hash keyed) to limit one event per cooldown window per visitor

Security reports: security@menj.net — please do not open public issues for vulnerabilities.


Privacy

See readme.txt for the user-facing privacy policy. Technical detail:

  • Default cooldown mode is session — a transient is keyed to the PHP session ID, no IP stored.
  • ip_hash mode stores hash('sha256', $ip . $salt) where $salt is a site-specific AUTH_KEY. This is a one-way hash. Whether it constitutes personal data under GDPR Article 4(1) depends on jurisdiction — consult your DPA.
  • No data leaves the server. No external connections.

Contributing

git clone https://github.com/menj/cite
cd cite
# No build step required — plain PHP/JS/CSS

Run Plugin Check before submitting a PR:

wp plugin check citepress

License

GPL v2 or later — https://www.gnu.org/licenses/gpl-2.0.html

About

Professional academic citation plugin with export formats (BibTeX, RIS, EndNote, CSL-JSON, CFF, Dublin Core), analytics, JSON-LD structured data, and citation formats.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors