@prefix sioc: <http://rdfs.org/sioc/ns#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix content: <http://purl.org/rss/1.0/modules/content/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<https://lhero.org/?post_type=lh-portfolio&#038;p=147190>
  a sioc:Post ;
  dc:title "Changelog" ;
  dcterms:identifier 147190 ;
  dc:modified "2026-09-11T16:03:41Z"^^xsd:dateTime ;
  dc:created "2026-09-09T16:13:29Z"^^xsd:dateTime ;
  sioc:link <https://lhero.org/portfolio/lh-vcard/changelog/> ;
  sioc:has_creator <https://lhero.org/author/1/#account> ;
  sioc:has_container <https://lhero.org/#posts> ;
  content:encoded """<ul class="lh_portfolio-meta"><li><strong>Type:</strong> Doc-section</li><li><strong>Part of:</strong> <a href="https://lhero.org/portfolio/lh-vcard/">LH Vcard</a></li></ul><h3>1.07</h3>
<ul>
<li>Removed a stray <code>error_log</code> file (a benign Composer-internal PHP deprecation notice, no sensitive content) that got accidentally committed in the initial GitHub push</li>
<li>Added <code>.gitignore</code> to prevent recurrence</li>
</ul>
<h3>1.06</h3>
<ul>
<li>Split the LH_Vcard_plugin god class into one class per concern: LH_Vcard_Signed_Link (tokens), LH_Vcard_Builder (vCard content), LH_Vcard_Rest_Controller (REST route), LH_Vcard_Recently_Updated (tracking/widget/shortcode/backfill), LH_Vcard_Integrations (third-party UI touchpoints)</li>
<li>LH_Vcard_plugin shrinks to a thin bootstrap; return_signed_vcard_download_url() kept in place as a one-line proxy since lh-profile-page&#8217;s search-users MCP ability calls that exact class/method directly</li>
<li>Verified byte-for-byte identical vCard output before/after (aside from sabre/vobject&#8217;s own randomly-regenerated UID field)</li>
</ul>
<h3>1.05</h3>
<ul>
<li>Removed dead code (unused curpageurl() method, zero call sites)</li>
<li>Added License/License URI to the plugin header</li>
<li>Escaped remaining unescaped output (esc_url/esc_html__/esc_html_e/absint) across every link-building method</li>
<li>Added readme.txt (previously empty) now that the plugin is public</li>
</ul>
<h3>1.04</h3>
<ul>
<li>Replaced the bundled, unattributed <code>includes/wp-gvc-cf-vcard.class.php</code> library with <code>sabre/vobject</code> (Packagist, actively maintained), via a new plugin-level <code>composer.json</code></li>
<li>Fixed two latent bugs from the old library: a malformed 8-component <code>ADR</code> line (RFC 6350 specifies 7), and missing RFC-required line-folding on long <code>PHOTO</code> data</li>
<li>Swapped <code>file_get_contents()</code>/<code>parse_url()</code> for <code>wp_remote_get()</code>/<code>wp_parse_url()</code> in the photo-fetching helpers, and <code>date()</code> for <code>gmdate()</code></li>
<li>Output remains vCard 3.0 (not sabre/vobject&#8217;s 4.0 default), for compatibility with older phone contact-import apps</li>
<li>Deleted <code>includes/wp-gvc-cf-vcard.class.php</code> once nothing referenced it any longer</li>
<li>Verified against a live-generated vCard: correct FN/N/EMAIL/TEL/URL, properly folded base64 PHOTO data</li>
</ul>
<h3>1.03</h3>
<ul>
<li>Switched every remaining vCard link caller (user row actions, BuddyPress member/group links, group reports, dashboard widget, bbPress reply links) to the signed REST download mechanism</li>
<li>Removed the old admin-ajax path entirely: <code>return_vcard_link()</code>, <code>ajax_do_vcard()</code>, its <code>wp_ajax_</code> hook, <code>maybe_return_object()</code>, <code>handle_singular_user_id()</code>, <code>handle_multiple_user_id()</code></li>
<li>Raised the signed link&#8217;s default TTL from 15 minutes to 24 hours (<code>DAY_IN_SECONDS</code>), now that the mechanism serves long-lived UI surfaces, not just freshly-generated links</li>
</ul>
<h3>1.02</h3>
<ul>
<li>Added a signed, non-user-specific vCard download mechanism: <code>return_signed_vcard_download_url()</code>, an HMAC token independent of who&#8217;s logged in, served via a new <code>GET /wp-json/lh-vcard/v1/download</code> REST route</li>
<li>Initially wired into exactly one caller: the <code>lh-profile-page/search-users</code> MCP ability&#8217;s <code>vcard_url</code> output field, so a link generated by that ability&#8217;s own account works when opened in anyone else&#8217;s browser</li>
<li>Old <code>return_vcard_link()</code>/admin-ajax path left untouched for every existing UI caller at this point</li>
</ul>
<h3>Earlier</h3>
<p>Vcard export via <code>admin-ajax.php?action=lh_vcard-do_vcard</code>, gated by login + a <code>publish_posts</code> capability check (a nonce was attached to the URL but never actually verified server-side). BuddyPress member/group integration, group reports column, dashboard widget of recently-updated vCards, <code><ul><li><a href="https://lhero.org/members/1015992/">https://lhero.org/wp-content/plugins/lh-gravatar-cache/icons/unknown_user.svg</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015992&#038;token=1789196582.1838aa277b74b1d4915e5097cb75a8a9"  download="vcard_1015992.vcf">aergsd</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015992">activity</a></li><li><a href="https://lhero.org/members/1015991/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015991&#038;token=1789196582.36c556d4db788575edb24113affef678"  download="vcard_1015991.vcf">w4ergqwer wergw</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015991">activity</a></li><li><a href="https://lhero.org/members/1015989/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015989&#038;token=1789196582.fcf9d7a7be14c9189f753790a4edd9c8"  download="vcard_1015989.vcf">Mathew Coggan</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015989">activity</a></li><li><a href="https://lhero.org/members/1015988/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/female.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015988&#038;token=1789196582.cafb126b2442c87ecc563d4d94470743"  download="vcard_1015988.vcf">Janelle Wensor</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015988">activity</a></li><li><a href="https://lhero.org/members/1015986/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015986&#038;token=1789196582.1fb74b9dffd715ba7ecefbddc6d20005"  download="vcard_1015986.vcf">Oscar Fau</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015986">activity</a></li><li><a href="https://lhero.org/members/1015985/">https://lhero.org/wp-content/lh-gravatar-cache/b8023d02fcfeee172986aa3321d8336d/535bebda9c760295278dcda91edc52b0-gravatar.jpg</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015985&#038;token=1789196582.f3263dbe4e3e4b1b1364d7be1864cc22"  download="vcard_1015985.vcf">Rebekah Helm</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015985">activity</a></li><li><a href="https://lhero.org/members/1015982/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/female.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015982&#038;token=1789196582.88bc7e9487cca0c525162e25066c4b26"  download="vcard_1015982.vcf">Teresa Gray</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015982">activity</a></li><li><a href="https://lhero.org/members/1015981/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/female.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015981&#038;token=1789196582.40d1bc7fb600eebc8d22c99d1b09bcb5"  download="vcard_1015981.vcf">Lora Rogers</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015981">activity</a></li><li><a href="https://lhero.org/members/1015980/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015980&#038;token=1789196582.812934cce2e43a07f92fa5a75a9c1ed2"  download="vcard_1015980.vcf">Liam Rogers</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015980">activity</a></li><li><a href="https://lhero.org/members/1015976/">https://lhero.org/wp-content/lh-google-login/google/731a25bee45d57e9208aa622c1b39fbd/73d2154f2b5efa7de5bff178d0c671c4-google_avatar.jpg</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015976&#038;token=1789196582.5a08fee9308d89a018842e7836f59f1b"  download="vcard_1015976.vcf">Sophie Ng</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015976">activity</a></li><li><a href="https://lhero.org/members/1015978/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015978&#038;token=1789196582.e60a02f39b6d93036b58a6742702b9ba"  download="vcard_1015978.vcf">Blake Hohenhaus</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015978">activity</a></li><li><a href="https://lhero.org/members/1015975/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/female.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015975&#038;token=1789196582.e386b39a4a0b19cfacf87b3d19cbbf08"  download="vcard_1015975.vcf">Cara Cara Gillespie</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015975">activity</a></li><li><a href="https://lhero.org/members/1015974/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015974&#038;token=1789196582.7e6e4a0fba1da9658ef9621ab9a534ce"  download="vcard_1015974.vcf">Dylan Johnstone</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015974">activity</a></li><li><a href="https://lhero.org/members/1015973/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/female.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015973&#038;token=1789196582.59c6a66a542c7b9b98e5f917a6cc0d15"  download="vcard_1015973.vcf">Bimaya Raddalgoda</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015973">activity</a></li><li><a href="https://lhero.org/members/1015937/">https://lhero.org/wp-content/lh-google-login/google/2d919aa09c12e7908440dc0a7a92014a/d8dd50e277d54dd1692c95001a7b6c06-google_avatar.jpg</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015937&#038;token=1789196582.5c4a7e70cf4667250534f448744d8cb5"  download="vcard_1015937.vcf">Mal Foki</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015937">activity</a></li><li><a href="https://lhero.org/members/1015934/">https://lhero.org/wp-content/lh-google-login/google/1cd9b308fb16ffa280193cefdab0c30a/73d2154f2b5efa7de5bff178d0c671c4-google_avatar.jpg</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015934&#038;token=1789196582.daa193b6ce3692ee94e5b1fcd6a0fcd8"  download="vcard_1015934.vcf">Seb Gotch</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015934">activity</a></li><li><a href="https://lhero.org/members/1015972/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/female.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015972&#038;token=1789196582.67917bfb064f1ff3f64e80dee1165a7d"  download="vcard_1015972.vcf">Laura Bingham</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015972">activity</a></li><li><a href="https://lhero.org/members/1015971/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015971&#038;token=1789196582.518807e2abdea4a4e98d3784e4bef9c0"  download="vcard_1015971.vcf">Matt Ploenzke</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015971">activity</a></li><li><a href="https://lhero.org/members/1015970/">https://lhero.org/wp-content/plugins/lh-profile-page/assets/male.png</a><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015970&#038;token=1789196582.542a52911691715e722370ad0e29d4c5"  download="vcard_1015970.vcf">Ralph Pabelico</a> <a href="https://lhero.org/wp-admin/network/admin.php?page=lh-user-audit-log&#038;user_id=1015970">activity</a></li></ul><p><a rel="nofollow" href="https://lhero.org/wp-json/lh-vcard/v1/download?user_ids=1015934%2C1015937%2C1015970%2C1015971%2C1015972%2C1015973%2C1015974%2C1015975%2C1015976%2C1015978%2C1015980%2C1015981%2C1015982%2C1015985%2C1015986%2C1015988%2C1015989%2C1015991%2C1015992&#038;token=1789196582.ba5ef895b1fd57714394c452021cbd41"  download="vcard_1015992,1015991,1015989,1015988,1015986,1015985,1015982,1015981,1015980,1015976,1015978,1015975,1015974,1015973,1015937,1015934,1015972,1015971,1015970.vcf">All Users</a></p></code> shortcode.</p>
"""^^rdf:XMLLiteral ;
  sioc:content """Type: Doc-sectionPart of: LH Vcard1.07

Removed a stray error_log file (a benign Composer-internal PHP deprecation notice, no sensitive content) that got accidentally committed in the initial GitHub push
Added .gitignore to prevent recurrence

1.06

Split the LH_Vcard_plugin god class into one class per concern: LH_Vcard_Signed_Link (tokens), LH_Vcard_Builder (vCard content), LH_Vcard_Rest_Controller (REST route), LH_Vcard_Recently_Updated (tracking/widget/shortcode/backfill), LH_Vcard_Integrations (third-party UI touchpoints)
LH_Vcard_plugin shrinks to a thin bootstrap; return_signed_vcard_download_url() kept in place as a one-line proxy since lh-profile-page&#8217;s search-users MCP ability calls that exact class/method directly
Verified byte-for-byte identical vCard output before/after (aside from sabre/vobject&#8217;s own randomly-regenerated UID field)

1.05

Removed dead code (unused curpageurl() method, zero call sites)
Added License/License URI to the plugin header
Escaped remaining unescaped output (esc_url/esc_html__/esc_html_e/absint) across every link-building method
Added readme.txt (previously empty) now that the plugin is public

1.04

Replaced the bundled, unattributed includes/wp-gvc-cf-vcard.class.php library with sabre/vobject (Packagist, actively maintained), via a new plugin-level composer.json
Fixed two latent bugs from the old library: a malformed 8-component ADR line (RFC 6350 specifies 7), and missing RFC-required line-folding on long PHOTO data
Swapped file_get_contents()/parse_url() for wp_remote_get()/wp_parse_url() in the photo-fetching helpers, and date() for gmdate()
Output remains vCard 3.0 (not sabre/vobject&#8217;s 4.0 default), for compatibility with older phone contact-import apps
Deleted includes/wp-gvc-cf-vcard.class.php once nothing referenced it any longer
Verified against a live-generated vCard: correct FN/N/EMAIL/TEL/URL, properly folded base64 PHOTO data

1.03

Switched every remaining vCard link caller (user row actions, BuddyPress member/group links, group reports, dashboard widget, bbPress reply links) to the signed REST download mechanism
Removed the old admin-ajax path entirely: return_vcard_link(), ajax_do_vcard(), its wp_ajax_ hook, maybe_return_object(), handle_singular_user_id(), handle_multiple_user_id()
Raised the signed link&#8217;s default TTL from 15 minutes to 24 hours (DAY_IN_SECONDS), now that the mechanism serves long-lived UI surfaces, not just freshly-generated links

1.02

Added a signed, non-user-specific vCard download mechanism: return_signed_vcard_download_url(), an HMAC token independent of who&#8217;s logged in, served via a new GET /wp-json/lh-vcard/v1/download REST route
Initially wired into exactly one caller: the lh-profile-page/search-users MCP ability&#8217;s vcard_url output field, so a link generated by that ability&#8217;s own account works when opened in anyone else&#8217;s browser
Old return_vcard_link()/admin-ajax path left untouched for every existing UI caller at this point

Earlier
Vcard export via admin-ajax.php?action=lh_vcard-do_vcard, gated by login + a publish_posts capability check (a nonce was attached to the URL but never actually verified server-side). BuddyPress member/group integration, group reports column, dashboard widget of recently-updated vCards, https://lhero.org/wp-content/plugins/lh-gravatar-cache/icons/unknown_user.svgaergsd activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngw4ergqwer wergw activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngMathew Coggan activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/female.pngJanelle Wensor activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngOscar Fau activityhttps://lhero.org/wp-content/lh-gravatar-cache/b8023d02fcfeee172986aa3321d8336d/535bebda9c760295278dcda91edc52b0-gravatar.jpgRebekah Helm activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/female.pngTeresa Gray activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/female.pngLora Rogers activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngLiam Rogers activityhttps://lhero.org/wp-content/lh-google-login/google/731a25bee45d57e9208aa622c1b39fbd/73d2154f2b5efa7de5bff178d0c671c4-google_avatar.jpgSophie Ng activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngBlake Hohenhaus activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/female.pngCara Cara Gillespie activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngDylan Johnstone activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/female.pngBimaya Raddalgoda activityhttps://lhero.org/wp-content/lh-google-login/google/2d919aa09c12e7908440dc0a7a92014a/d8dd50e277d54dd1692c95001a7b6c06-google_avatar.jpgMal Foki activityhttps://lhero.org/wp-content/lh-google-login/google/1cd9b308fb16ffa280193cefdab0c30a/73d2154f2b5efa7de5bff178d0c671c4-google_avatar.jpgSeb Gotch activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/female.pngLaura Bingham activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngMatt Ploenzke activityhttps://lhero.org/wp-content/plugins/lh-profile-page/assets/male.pngRalph Pabelico activityAll Users shortcode.
""" ;
  sioc:topic <https://lhero.org/lh_portfolio-type/doc-section/>, <https://lhero.org/?taxonomy=author&term=cap-1> .

<https://lhero.org/author/1/#account> rdfs:seeAlso <https://lhero.org/author/1/?feed=lhrdf&format=turtle> .
<https://lhero.org/lh_portfolio-type/doc-section/> rdfs:seeAlso <https://lhero.org/lh_portfolio-type/doc-section/?feed=lhrdf&format=turtle> .
<https://lhero.org/?taxonomy=author&term=cap-1> rdfs:seeAlso <https://lhero.org/?taxonomy=author&term=cap-1&feed=lhrdf&format=turtle> .
