Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/MODS/PTMatrix/SEO.pm
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/MODS/PTMatrix/SEO.pm

added on local at 2026-07-01 12:34:24

Added
+144
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 55ff19302294
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1package MODS::PTMatrix::SEO;
2#======================================================================
3# TaskForge -- SEO + Open Graph + Twitter Card helper.
4#
5# Single scope (for now): platform SEO -- rows in platform_settings
6# keyed by setting_key ('seo.title', 'seo.description', 'seo.og_image',
7# 'seo.canonical_base', 'seo.robots_index', etc.).
8#
9# render_tags() builds the <meta> block (title + description + OG +
10# twitter) the marketing wrapper injects into <head>. Sensible fallbacks
11# at every layer so a brand-new install still emits good defaults.
12#
13# Adapted from MODS::WebSTLs::SEO with the per-storefront block removed
14# (TaskForge isn't multi-tenant on the marketing side) and the
15# platform_settings UPSERT adjusted to TF's table shape (no id PK, no
16# updated_by_user_id column -- setting_key IS the PK).
17#======================================================================
18use strict;
19use warnings;
20
21sub new {
22 my ($class, %args) = @_;
23 return bless({}, $class);
24}
25
26# ---- Schema readiness ------------------------------------------------
27sub platform_settings_ready {
28 my ($self, $db, $dbh, $DB) = @_;
29 my $r = $db->db_readwrite($dbh, qq~
30 SELECT COUNT(*) AS n FROM information_schema.tables
31 WHERE table_schema='$DB' AND table_name='platform_settings'
32 ~, $ENV{SCRIPT_NAME}, __LINE__);
33 return ($r && $r->{n}) ? 1 : 0;
34}
35
36# ---- Platform-wide SEO ----------------------------------------------
37sub get_platform_settings {
38 my ($self, $db, $dbh, $DB) = @_;
39 return {} unless $self->platform_settings_ready($db, $dbh, $DB);
40 my @rows = $db->db_readwrite_multiple($dbh, qq~
41 SELECT setting_key, setting_value
42 FROM `${DB}`.platform_settings
43 WHERE setting_key LIKE 'seo.%'
44 ~, $ENV{SCRIPT_NAME}, __LINE__);
45 my %out;
46 foreach my $r (@rows) {
47 $out{ $r->{setting_key} } = $r->{setting_value};
48 }
49 return \%out;
50}
51
52sub set_platform_setting {
53 my ($self, $db, $dbh, $DB, $key, $value, $actor_uid) = @_;
54 return 0 unless $key;
55 return 0 unless $self->platform_settings_ready($db, $dbh, $DB);
56
57 # Dotted lowercase + alphanumeric/underscore only.
58 $key =~ s/[^a-z0-9._]//g;
59 $key = substr($key, 0, 80);
60 return 0 unless length $key;
61
62 my $sv = defined $value ? $value : '';
63 $sv =~ s/'/''/g;
64
65 # TF platform_settings has no updated_by_user_id column -- ignore the
66 # actor param. Kept in the signature for caller-side symmetry with
67 # WebSTLs's set_platform_setting.
68 $db->db_readwrite($dbh, qq~
69 INSERT INTO `${DB}`.platform_settings
70 SET setting_key='$key',
71 setting_value='$sv'
72 ON DUPLICATE KEY UPDATE
73 setting_value='$sv'
74 ~, $ENV{SCRIPT_NAME}, __LINE__);
75 return 1;
76}
77
78# ---- Tag rendering ---------------------------------------------------
79# Builds the head <meta> block for the marketing pages (landing,
80# pricing, features, etc.) and any other page that wants Open Graph
81# / Twitter Card metadata. Pulls from platform_settings and falls
82# back to sensible defaults.
83sub render_tags {
84 my ($self, %a) = @_;
85 my $platform = $a{platform} || {}; # hash from get_platform_settings()
86 my $page_title = $a{page_title} || '';
87 my $request_url = $a{request_url} || '';
88
89 my $title = $page_title || $platform->{'seo.title'} || 'PTMatrix';
90 my $description = $platform->{'seo.description'} || '';
91 my $keywords = $platform->{'seo.keywords'} || '';
92 my $canonical = $request_url || $platform->{'seo.canonical_base'} || '';
93 my $og_image = $platform->{'seo.og_image'} || '';
94 my $og_site = $platform->{'seo.og_site_name'}|| 'PTMatrix';
95 my $twit_card = $platform->{'seo.twitter_card'}|| 'summary_large_image';
96 my $twit_site = $platform->{'seo.twitter_site'}|| '';
97
98 my $robots_on = 1;
99 if (defined $platform->{'seo.robots_index'} && $platform->{'seo.robots_index'} eq '0') {
100 $robots_on = 0;
101 }
102 my $robots_str = $robots_on ? 'index,follow' : 'noindex,nofollow';
103
104 # If og_image is a relative path and we have a canonical base, prefix it.
105 if ($og_image && $og_image =~ m{^/} && $platform->{'seo.canonical_base'}) {
106 my $base = $platform->{'seo.canonical_base'};
107 $base =~ s{/+$}{};
108 $og_image = $base . $og_image;
109 }
110
111 foreach ($title, $description, $keywords, $canonical, $og_image, $og_site, $twit_card, $twit_site) {
112 $_ = '' unless defined $_;
113 s/&/&amp;/g; s/</&lt;/g; s/>/&gt;/g; s/"/&quot;/g;
114 }
115
116 my $h = '';
117 $h .= qq~ <meta name="description" content="$description">\n~ if length $description;
118 $h .= qq~ <meta name="keywords" content="$keywords">\n~ if length $keywords;
119 $h .= qq~ <meta name="robots" content="$robots_str">\n~;
120 $h .= qq~ <link rel="canonical" href="$canonical">\n~ if length $canonical;
121 $h .= qq~ <meta property="og:title" content="$title">\n~;
122 $h .= qq~ <meta property="og:description" content="$description">\n~ if length $description;
123 $h .= qq~ <meta property="og:type" content="website">\n~;
124 $h .= qq~ <meta property="og:site_name" content="$og_site">\n~ if length $og_site;
125 $h .= qq~ <meta property="og:image" content="$og_image">\n~ if length $og_image;
126 $h .= qq~ <meta property="og:url" content="$canonical">\n~ if length $canonical;
127 $h .= qq~ <meta name="twitter:card" content="$twit_card">\n~;
128 $h .= qq~ <meta name="twitter:title" content="$title">\n~;
129 $h .= qq~ <meta name="twitter:description" content="$description">\n~ if length $description;
130 $h .= qq~ <meta name="twitter:image" content="$og_image">\n~ if length $og_image;
131 $h .= qq~ <meta name="twitter:site" content="$twit_site">\n~ if length $twit_site;
132 return $h;
133}
134
135sub _trim {
136 my ($s, $max) = @_;
137 return '' unless defined $s;
138 $s =~ s/^\s+|\s+$//g;
139 $s =~ s/[\r\n]+/ /g;
140 $max ||= 255;
141 return substr($s, 0, $max);
142}
143
1441;
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help