Developer guide
Add-on ID kv_product_videos, addon.xml scheme 3.0, priority 1500. No core files are changed. Overrides are not used. Everything runs through PHP hooks, a backend post-controller, template hooks and schema extensions.
File map
Section titled “File map”app/addons/kv_product_videos/ addon.xml settings, install/uninstall SQL init.php hook registration func.php public API + hook handlers controllers/backend/products.post.php "Videos" tab on products.update / products.add controllers/backend/kv_product_videos_license.php "Check now" / cron license check schemas/exim/products.post.php "Videos" import/export field schemas/exim/products.functions.php import/export functions schemas/block_manager/blocks.post.php product block cache invalidation + license cache key Tygh/Addons/KvProductVideos/VideoSource.php URL parsing, embed/watch URL builders Tygh/Addons/KvProductVideos/MetadataFetcher.php oEmbed title/duration + poster download Tygh/Addons/KvProductVideos/License/Client.php license check client (generated from the shared Kavio Labs template)design/backend/templates/addons/kv_product_videos/ hooks/products/tabs_content.post.tpl tab inside the product form hooks/index/styles.post.tpl views/components/videos_tab.tpl license/info.tpl License tab (points to the card) license/card.tpl License card (add-on page) / banner license/status_bar.tpl status chip/banner (not used: no own admin pages) license/badge.tpl badge in Add-ons > Manage add-ons hooks/addons/{tabs_content.pre,manage.post}.tpl card, badge hooks/index/styles.post.tpl add-on styles + license.less (kv-license block)design/backend/css/addons/kv_product_videos/styles.lessdesign/backend/css/addons/kv_product_videos/license.less card, chip and badge stylesvar/themes_repository/responsive/templates/addons/kv_product_videos/ hooks/products/product_images.post.tpl gallery slides + thumbnails (data container) hooks/products/product_data.post.tpl product list marker (badge / hover) hooks/products/layout_content.post.tpl VideoObject JSON-LD hooks/index/{styles,scripts}.post.tpl blocks/product_tabs/kv_product_videos.tpl "Videos" product tab components/player.tpl click-to-play player markupvar/themes_repository/responsive/css/addons/kv_product_videos/styles.lessjs/addons/kv_product_videos/func.jsvar/langs/en/addons/kv_product_videos.poData model
Section titled “Data model”cscart_kv_product_videos
| Column | Type | Notes |
|---|---|---|
video_id |
int unsigned, PK, AI | |
product_id |
int unsigned | indexed together with status and position |
source_type |
varchar(16) | youtube, vimeo, url (self-hosted link) or file (uploaded) |
source_url |
varchar(1024) | the link as entered (empty for file) |
video_key |
varchar(128) | YouTube ID, or Vimeo id / id:hash |
file_path |
varchar(255) | path inside the kv_product_videos storage (<product_id>/<file>) |
mime_type |
varchar(64) | for url and file |
start_at |
int unsigned | start offset in seconds parsed from the link |
duration |
int unsigned | seconds (from Vimeo oEmbed); used in JSON-LD |
position |
int | sort order |
status |
char(1) | A active / D disabled |
timestamp, updated_timestamp |
int unsigned | creation date is used as uploadDate |
cscart_kv_product_videos_descriptions has the columns video_id, lang_code and title. The primary key is (video_id, lang_code).
Preview images are standard CS-Cart image pairs with the object type kv_product_video. The object ID is video_id and the type is M. Only the detailed image is stored, and thumbnails are generated by core.
Uploaded files go through the CS-Cart storage API. The storage type kv_product_videos (prefix images/kv_product_videos, root dir, CDN-enabled) is registered on demand by fn_kv_product_videos_get_storage(). PHP execution in images/ is already blocked by the core .htaccess.
PHP hooks used
Section titled “PHP hooks used”| Hook | Handler | Purpose |
|---|---|---|
update_product_post |
fn_kv_product_videos_update_product_post |
Saves the Videos tab. It runs only in the admin area, for the submitted product ($_REQUEST['product_id'] or a new product), only when product_data[kv_pv_submitted]=Y is present, once per request, and only if fn_kv_product_videos_can_edit_product() allows it. |
delete_product_post |
fn_kv_product_videos_delete_product_post |
Deletes videos, posters and the product’s upload folder. |
clone_product |
fn_kv_product_videos_clone_product |
Copies rows, all-language titles, posters (fn_clone_image_pairs) and uploaded files. |
get_products_post |
fn_kv_product_videos_get_products_post |
Storefront only. One query per product list. Adds $product['kv_pv_list'] (count, badge flag, hover source). |
before_dispatch |
static License\Client::onBeforeDispatch |
Admin area and console only: runs the due license check and shows license notices. |
addons:tabs_content |
pre, admin template | License card at the top of the add-on page. |
addons:manage |
post, admin template | License badge, moved into the add-on’s row (4.17–4.21 have no per-row hook). |
index:styles |
post, admin template | license.less (kv-license block). |
The kv_pv_submitted flag is printed after all rows of the tab. If PHP truncates the request because of max_input_vars, the flag is missing and nothing is changed or deleted.
Hooks provided
Section titled “Hooks provided”fn_set_hook('kv_product_videos_get_videos_pre', $product_id, $params, $lang_code);fn_set_hook('kv_product_videos_get_videos_post', $product_id, $params, $lang_code, $videos);fn_set_hook('kv_product_videos_prepare_video_post', $video);fn_set_hook('kv_product_videos_update_video_pre', $product_id, $data, $video_id, $lang_code, $video_file, $thumbnail_file);fn_set_hook('kv_product_videos_update_video_post', $product_id, $data, $video_id, $lang_code, $record);fn_set_hook('kv_product_videos_delete_video_pre', $video_id, $video);fn_set_hook('kv_product_videos_get_structured_data_item', $product, $video, $item);Register them in your add-on’s init.php as usual. For example, fn_register_hooks('kv_product_videos_get_structured_data_item') with fn_<your_addon>_kv_product_videos_get_structured_data_item(&$product, &$video, &$item).
Public functions
Section titled “Public functions”| Function | Returns |
|---|---|
fn_kv_product_videos_get_videos($product_id, $params = [], $lang_code) |
Videos keyed by video_id. $params: status (string or array), limit, get_images |
fn_kv_product_videos_get_product_videos($product_id, $lang_code) |
Active videos prepared for the storefront: embed_url, embed_url_muted, background_url, src, play_src, watch_url, is_embed, main_pair. Respects Maximum videos. Cached per request. Usable as a Smarty modifier. |
fn_kv_product_videos_update_video($product_id, $data, $video_id = 0, $lang_code, $video_file = [], $thumbnail_file = [], $fetch_metadata = null) |
Creates ($video_id = 0) or updates a video. $data: source_url, title, position, status, delete_thumbnail. Returns video_id or false |
fn_kv_product_videos_delete_video($video_id) / fn_kv_product_videos_delete_product_videos($product_id) |
|
fn_kv_product_videos_clone_videos($from_product_id, $to_product_id) |
|
fn_kv_product_videos_get_structured_data($product) |
JSON strings (one VideoObject per video), safe to print inside a script tag |
fn_kv_product_videos_can_edit_product($product_id) |
Edition-aware permission check (MV vendor ownership, CS-Cart shared products) |
\Tygh\Addons\KvProductVideos\VideoSource::parseUrl($url) |
['source_type','video_key','start_at','mime_type','source_url'] or null |
Example: add a YouTube video from your own code.
fn_kv_product_videos_update_video($product_id, [ 'source_url' => 'https://youtu.be/aqz-KE-bpKQ', 'title' => 'Product overview', 'status' => 'A',], 0, DESCR_SL);Storefront integration
Section titled “Storefront integration”The core gallery template views/products/components/product_images.tpl has no hooks inside the gallery, and the add-on does not override it. Instead:
hooks/products/product_images.post.tplrenders a hidden container.kv-pv-gallery-dataafter the gallery. It holds the video slides (.kv-pv-player), the thumbnail items, and a thumbnail-strip shell for products with a single image. It also contains<script class="cm-ajax-force">.js/addons/kv_product_videos/func.jsruns from the bottom of the page, before DOM ready. After AJAX reloads (option changes, quick view) the forced inline script runs it too, before$.commonInit(). It moves the slides into.cm-preview-wrapperand the thumbnails into#images_preview_<id>at the same index. Then it renumbersdata-ca-image-order.- The core Owl carousel then starts as usual. The add-on listens to
ce.product_image_gallery.readyandce.product_image_gallery.image_changed. On these events it reveals the slides, stops videos that are no longer visible and handles muted autoplay. - If the markup is unknown or the carousel is already running, the container is shown as a plain list of players below the images. This is the same as the Below the product images placement.
A player is a <button> poster. The iframe (youtube-nocookie.com or player.vimeo.com with dnt=1) or the <video> element is created only on click. The iframe URL is always built from a validated video ID and never from raw user input.
JSON-LD is printed by hooks/products/layout_content.post.tpl. The tag has data-no-defer and data-no-execute="§", so CS-Cart neither moves it nor evaluates it during AJAX updates.
JS API
Section titled “JS API”Tygh.kvProductVideos.scan(context); // integrate pending gallery containersTygh.kvProductVideos.initLists(context); // product list badges/hoverTygh.kvProductVideos.play(element, muted);Tygh.kvProductVideos.stop(element);All classes start with kv-pv-. The player keeps the product image box ratio through an inline padding-top. Without a known size it falls back to 16:9. Override the styles in your theme’s custom CSS.
Caching
Section titled “Caching”The add-on extends blocks.post.php with the kv_product_videos and kv_product_videos_descriptions tables. Cached product blocks (badge and hover markers) therefore refresh when videos change. It also adds the license state to the products block cache key (callable_handlers kv_pv_license → fn_kv_product_videos_is_licensed).
License check
Section titled “License check”Tygh\Addons\KvProductVideos\License\Client verifies the license against the Kavio Labs license server. The client, the “Check now”/cron controller controllers/backend/kv_product_videos_license.php and the License tab template design/backend/templates/addons/kv_product_videos/license/info.tpl are generated from the shared Kavio Labs template; don’t edit them by hand.
- Admin license UI (generated, REVISION 3; don’t edit by hand):
design/backend/templates/addons/kv_product_videos/license/card.tpl(License card at the top of the add-on page, every tab;hooks/addons/tabs_content.pre.tpl),license/badge.tpl(badge in Add-ons > Manage add-ons;hooks/addons/manage.post.tpl),license/status_bar.tpl(chip/banner for the add-on’s own admin pages),design/backend/css/addons/kv_product_videos/license.less(loaded by the kv-license block ofhooks/index/styles.post.tpl). The add-on has no admin pages of its own, so it uses no status bar. All of them render nothing for vendors and for administrators without the “Manage settings” privilege. The “what is switched off” sentence is the language variablekv_product_videos.license_off_effect.License\Clientassigns the view data to$kv_lic_kv_product_videosin itsbefore_dispatchhandler (only for administrators with the Manage settings privilege); the templates only read this variable, so they render nothing (and no error) while an older version of the PHP code is loaded, for example in the request that uploads a new zip over the installed add-on. Templates never call add-on functions; PHP code can callfn_kv_product_videos_license_ui($context). fn_kv_product_videos_is_licensed()(func.php) is the gate. It reads the stored state only and never contacts the server.fn_kv_product_videos_get_product_videos()returns no videos (gallery slides and thumbnails, the “Videos” tab, VideoObject structured data);fn_kv_product_videos_get_products_post()adds no list badge or hover data.- The core
productsblock is cached; its cache key includes the license state (blocks.post.php,callable_handlers), so the badge appears or disappears at once. - Admin video management (
fn_kv_product_videos_get_videos(), the Videos tab, import/export) is not gated. Core 4.21 product videos are not affected. Client::boot()(init.php) registers abefore_dispatchhandler for the admin area and the console only. On admin page views and console requests it runs the check when it is due (key changed, or 24 hours after the last check; 1 hour after a failure) and shows the admin notice once per session.- Request:
POST https://cs-cart.kaviolabs.com/index.php?dispatch=kv_licenses.checkwith JSON{"v":1, "license", "addon", "version", "hosts":[…], "platform":{"version", "edition"}, "nonce"}, sent throughTygh\Http(3 s connect, 5 s total; kept out of the CS-Cart request log). The answer is accepted only when its Ed25519 signature matches a public key embedded in the client and it echoes the nonce. - State: storage key
kv_license_kv_product_videos(?:storage_data), orvar/kv_storage/kv_product_videos/license.jsonwhile the full page cache is active, protected by an HMAC with the store’s crypt key. - Cron:
php admin.php --dispatch=kv_product_videos_license.check. - Development stores only (
http_hostandhttps_hostare development hosts): the constantsKV_LICENSE_SERVER_URL,KV_LICENSE_TEST_HOST(act as if the development storefronts ran on that host) andKV_LICENSE_TEST_TIME_OFFSET(seconds). They are ignored on production hosts.
Security notes
Section titled “Security notes”- All SQL goes through
db_*with placeholders. - Video and image uploads go through
fn_filter_uploaded_data(). Videos are limited tomp4,m4v,webmandogv. Images are limited to the formats the image library supports, and to the image size limit in 4.18+. - Links are accepted only with
http/httpsand without whitespace or quote characters. Titles are stripped of HTML. All template output is escaped. - Remote metadata requests run only in the admin area. They use short timeouts (5 s connect, 10 s total) and a host allow-list for downloaded images (
i.ytimg.com,img.youtube.com,i.vimeocdn.com). Failures are ignored. - Every hook handler is wrapped in
try/catch, so the add-on never breaks the storefront, product saving, cloning or deletion.
Third-party code
Section titled “Third-party code”Tygh/Addons/KvProductVideos/License/Ed25519.php (checks the signature of the license server’s answers on servers whose PHP has no sodium extension) is derived from paragonie/sodium_compat, ISC License, Copyright (c) 2016-2023 Paragon Initiative Enterprises and Copyright (c) 2013-2019 Frank Denis. The full license notice is in the file header.