contenir / contenir-social
Framework-free social profiles and share links for Contenir: a network catalogue, profile URL checks for twelve networks, share links with no third-party scripts, inline SVG icons and a small script for Copy link and Share.
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
Requires (Dev)
- contenir/contenir-qa-tools: 0.1.x-dev
- infection/infection: ^0.34.1
- phpunit/phpunit: ^11.5.42
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-21 03:58:24 UTC
README
Framework-free social profiles and share links for Contenir sites:
- a catalogue of twelve networks, with each one's profile addresses, settings key and icon;
- profile checks: an address in any form the network serves it in, or just the account, becomes the canonical https address, and anything else gets a message an editor can act on;
- share links built from the page's own metadata, with no third-party scripts, counts or cookies, so nothing reaches a network until a visitor clicks;
- escaped HTML for both, with inline SVG icons and accessible names;
- a small dependency-free script for "Copy link" and "Share…".
It has no framework dependency. Sites use it through an adapter, and the CMS through an add-on:
- Laminas MVC sites: contenir/contenir-social-laminas-mvc adds
socialProfiles()andshare()view helpers. - The CMS: the Social add-on (
contenir-extension/social) holds the settings, and checks each profile, as an address or just the account, as it is saved.
Requirements
- PHP 8.3, 8.4 or 8.5
Profiles
use Contenir\Social\Html\ProfileLinks; use Contenir\Social\Profiles; $profiles = Profiles::fromSettings($settings); // the site's settings, as nested arrays echo ProfileLinks::render($profiles);
writes, for each profile in the order below:
<ul class="social social--profiles"> <li class="social__item social__item--instagram"> <a class="social__link" href="https://www.instagram.com/yourname/" rel="me noopener" target="_blank" aria-label="Instagram (opens in a new tab)"><svg class="social__icon" …></svg></a> </li> </ul>
rel="me" tells the networks that check it (Mastodon, for one) that the site and the profile belong together.
Profiles also gives sameAs(), every address for schema.org, and twitterSite(), the X account as @yourname for
twitter:site.
| Network | Settings key | Accepts | Stored as |
|---|---|---|---|
social.social_facebook |
facebook.com, www., m., web., mbasic., fb.com; a page, pages/… or profile.php?id=…; or the username |
https://www.facebook.com/yourpage |
|
social.social_instagram |
instagram.com, www.; or the username | https://www.instagram.com/yourname/ |
|
social.social_linkedin |
linkedin.com, www. or a country (au.); company/, in/, school/, showcase/ or groups/ |
https://www.linkedin.com/company/yourname/ |
|
| X | social.social_twitter |
x.com, twitter.com, www., mobile.; or the username | https://x.com/yourname |
| YouTube | social.social_youtube |
youtube.com, www., m.; @handle, channel/UC…, c/…, user/…; or the handle |
https://www.youtube.com/@yourname |
| TikTok | social.social_tiktok |
tiktok.com, www., m.; or the username | https://www.tiktok.com/@yourname |
social.social_pinterest |
pinterest.com and its country domains (pinterest.com.au, au.pinterest.com…); or the username | https://www.pinterest.com/yourname/ |
|
| Threads | social.social_threads |
threads.com, threads.net, www.; or the username | https://www.threads.com/@yourname |
| Bluesky | social.social_bluesky |
bsky.app/profile/…; or the handle (yourname.bsky.social, or a domain) |
https://bsky.app/profile/yourname.bsky.social |
| Mastodon | social.social_mastodon |
any server's /@yourname; or @yourname@server |
https://server/@yourname |
| Vimeo | social.social_vimeo |
vimeo.com, www.; a user or channels/…; or the username |
https://vimeo.com/yourname |
| Behance | social.social_behance |
behance.net, www.; or the username | https://www.behance.net/yourname |
The first five keys are the ones the CMS's Social Media Links plugin used, so a site's existing settings read as they
are. That plugin stored Instagram, X (as Twitter) and Pinterest as usernames, sometimes with the @ its hint
suggested. Values are read as they are stored, an address in any form or just the account, and the full address is
built when a page is written, so stored values never need rewriting and templates that read them directly keep
working. LinkedIn needs the whole address, since a name alone could be a company or a person. A value that isn't a profile is left out, and Profiles::$problems says why, by key.
ProfileUrl::parse() reads one value (and throws), tryParse() returns null instead, and problem() returns the
message for an editor.
Share buttons
use Contenir\Social\Html\ShareButtons; use Contenir\Social\Script\ShareScript; use Contenir\Social\Share\Page; use Contenir\Social\Share\ShareConfig; use Contenir\Social\Share\ShareLinks; $page = new Page($canonicalUrl, $title, $description, $shareImage); // absolute addresses $links = (new ShareLinks(ShareConfig::fromSettings($settings)))->for($page); echo ShareButtons::render($links); echo ShareScript::tag($nonce); // once per page, for "Copy link" and "Share…"
| Button | Goes to | Settings name |
|---|---|---|
https://www.facebook.com/sharer/sharer.php?u=… |
facebook |
|
| X | https://x.com/intent/tweet?text=…&url=… |
x |
https://www.linkedin.com/sharing/share-offsite/?url=… |
linkedin |
|
https://wa.me/?text=… |
whatsapp |
|
mailto:?subject=…&body=… |
email |
|
| Bluesky | https://bsky.app/intent/compose?text=… |
bluesky |
| Threads | https://www.threads.com/intent/post?text=…&url=… |
threads |
https://www.reddit.com/submit?url=…&title=… |
reddit |
|
https://www.pinterest.com/pin/create/button/?url=…&media=…&description=…, only when the page has an image |
pinterest |
|
| Telegram | https://t.me/share/url?url=…&text=… |
telegram |
| Copy link | copies the address | copy |
| Share… | the browser's share sheet | share |
Facebook's documented share dialog needs a Facebook app ID; sharer.php needs none, and still works, but Facebook no
longer documents it.
ShareConfig::fromSettings() reads:
| Key | Holds | When missing |
|---|---|---|
extensions.social.share_networks |
the buttons, in order: a list of names, the CMS's repeating rows, or names separated by commas | Facebook, X, LinkedIn, Email, Copy link, Share…, also when none are chosen |
extensions.social.share_labels |
show each button's name beside its icon | on |
extensions.social.share_utm |
add utm_source (the button's name), utm_medium (social, or email) and utm_campaign=share to the shared address |
off |
Links to networks open a new tab and say so in their accessible name ("Share on X (opens in a new tab)"); Email opens the visitor's mail program. "Copy link" and "Share…" are buttons, written hidden.
The script
ShareScript::tag() writes resources/share.js inline, with the page's CSP nonce. It has no
dependencies, sends nothing anywhere, and does nothing if included twice. It:
- shows "Copy link", and on a click copies the address and shows "Copied" beside the button for two seconds, in a
role="status"element so screen readers announce it; - shows "Share…" only where the browser has a share sheet (
navigator.share), which is mostly phones, and opens it with the page's address and title.
Without the script, both buttons stay hidden and every other button still works.
HTML options
Both renderers take Html\Options:
new Options( icons: true, // draw icons labels: null, // names beside icons: null is profiles off, share buttons as the site's setting newTab: true, // open links in a new tab, and say so block: 'social', // the BEM block: social, social__item, social__link, social__button, social__icon… class: '', // more classes for the list );
Classes are {block}, {block}--profiles or {block}--share on the list, then {block}__item and
{block}__item--{network}, {block}__link, {block}__button, {block}__icon, {block}__label and
{block}__status. Pass block: 'footer' to drop them into a site's own styles. No CSS is included.
Icons are 24 by 24 and filled with currentColor, so they take the text colour; size them with CSS. They are hidden
from assistive technology, since each link or button carries the name. They come from Simple Icons and Tabler Icons:
see ICONS.md for their licences and the networks' brand guidelines.
Licence
BSD-3-Clause. See LICENSE.md. Icons: see ICONS.md.