initial
This commit is contained in:
+2294
File diff suppressed because it is too large
Load Diff
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('jetpack-connection', 'jetpack-script-data', 'react', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '174f7dde97b3cbbddea5');
|
||||
+1
File diff suppressed because one or more lines are too long
+17
File diff suppressed because one or more lines are too long
+20
@@ -0,0 +1,20 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* filesize
|
||||
*
|
||||
* @copyright 2024 Jason Mulligan <jason.mulligan@avoidwork.com>
|
||||
* @license BSD-3-Clause
|
||||
* @version 10.1.6
|
||||
*/
|
||||
|
||||
/**
|
||||
* react-router v7.15.1
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
+1
File diff suppressed because one or more lines are too long
+134
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"name": "videopress/video",
|
||||
"version": "0.1.0",
|
||||
"title": "VideoPress",
|
||||
"category": "media",
|
||||
"icon": "<svg viewBox='0 0 29 21' width='24' height='24' xmlns='http://www.w3.org/2000/svg'><path fill-rule='evenodd' clip-rule='evenodd' d='M2.79037 0.59375C4.0363 0.59375 5.13102 1.41658 5.47215 2.60947L8.8452 14.4044C8.8486 14.4164 8.85411 14.4273 8.86124 14.4368L12.8572 0.59375H15.0927H21.2721C25.6033 0.59375 28.5066 3.39892 28.5066 7.64565C28.5066 11.9411 25.5272 14.6196 21.0818 14.6196H18.1499H14.3719L13.6379 16.8813C12.9796 18.9095 11.0827 20.2839 8.94152 20.2839C6.80035 20.2839 4.90341 18.9095 4.24517 16.8813L0.137069 4.22276C-0.444671 2.43022 0.898038 0.59375 2.79037 0.59375ZM15.7374 10.4119H20.0156C21.8718 10.4119 22.9856 9.35018 22.9856 7.64565C22.9856 5.93137 21.8718 4.91839 20.0156 4.91839H17.5202L15.7374 10.4119Z'></path></svg>",
|
||||
"description": "Embed a video from your media library or upload a new one with VideoPress.",
|
||||
"supports": {
|
||||
"html": false,
|
||||
"align": true,
|
||||
"anchor": true,
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"autoplay": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"anchor": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"caption": {
|
||||
"type": "string"
|
||||
},
|
||||
"controls": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"loop": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"maxWidth": {
|
||||
"type": "string",
|
||||
"default": "100%"
|
||||
},
|
||||
"muted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"playsinline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"preload": {
|
||||
"type": "string",
|
||||
"default": "metadata"
|
||||
},
|
||||
"seekbarPlayedColor": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"seekbarLoadingColor": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"seekbarColor": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"useAverageColor": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"guid": {
|
||||
"type": "string"
|
||||
},
|
||||
"src": {
|
||||
"type": "string"
|
||||
},
|
||||
"cacheHtml": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"poster": {
|
||||
"type": "string"
|
||||
},
|
||||
"posterData": {
|
||||
"type": "object",
|
||||
"default": {}
|
||||
},
|
||||
"videoRatio": {
|
||||
"type": "number"
|
||||
},
|
||||
"tracks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"privacySetting": {
|
||||
"type": "number",
|
||||
"default": 1
|
||||
},
|
||||
"allowDownload": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"displayEmbed": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"rating": {
|
||||
"type": "string"
|
||||
},
|
||||
"isPrivate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isExample": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"duration": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"textdomain": "jetpack-videopress-pkg",
|
||||
"editorScript": "file:./index.js",
|
||||
"editorStyle": "file:./index.css",
|
||||
"style": "file:./style.css",
|
||||
"viewScript": "file:./view.js",
|
||||
"viewStyle": "file:./view.css"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('jetpack-connection', 'jetpack-script-data', 'jetpack-shared-stores', 'react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-dom-ready', 'wp-editor', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => 'a10c36b6c517fb483735');
|
||||
+1
File diff suppressed because one or more lines are too long
+10
File diff suppressed because one or more lines are too long
+9
@@ -0,0 +1,9 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* filesize
|
||||
*
|
||||
* @copyright 2024 Jason Mulligan <jason.mulligan@avoidwork.com>
|
||||
* @license BSD-3-Clause
|
||||
* @version 10.1.6
|
||||
*/
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('wp-dom-ready', 'wp-polyfill'), 'version' => 'd00d9230ef0a6910ff95');
|
||||
+1
@@ -0,0 +1 @@
|
||||
.wp-block-jetpack-videopress{position:relative}.wp-block-jetpack-videopress.jetpack-videopress-player.alignleft~.wp-block-jetpack-videopress.jetpack-videopress-player,.wp-block-jetpack-videopress.jetpack-videopress-player.alignright~.wp-block-jetpack-videopress.jetpack-videopress-player{clear:both}.wp-block-jetpack-videopress figcaption{color:#555;font-size:13px;margin-bottom:1em;margin-top:.5em;text-align:center}.is-dark-theme .wp-block-jetpack-videopress figcaption{color:hsla(0,0%,100%,.65)}.wp-block-jetpack-videopress .jetpack-videopress-player__wrapper{line-height:0;position:relative}.wp-block-jetpack-videopress .jetpack-videopress-player__overlay{cursor:pointer;height:100%;left:0;opacity:1;position:absolute;top:0;transition:opacity .3s ease-in-out;width:100%}.wp-block-jetpack-videopress .jetpack-videopress-player__overlay:hover{opacity:0}.wp-block-jetpack-videopress.wp-block-jetpack-videopress--has-max-width{margin:auto}.is-layout-flex .wp-block-jetpack-videopress.wp-block-jetpack-videopress--has-max-width{margin:0}
|
||||
+1
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";var e={7966(){},8490(e){e.exports=window.wp.domReady}},t={};function o(r){var n=t[r];if(void 0!==n)return n.exports;var s=t[r]={exports:{}};return e[r](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var r in t)o.o(t,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var r=o(8490),n=o.n(r);o(7966);n()(function(){!function(){const e=document.querySelectorAll(".wp-block-jetpack-videopress");0!==e.length&&e.forEach(function(e){const t=e.querySelector("iframe");if(!t)return;if(!window?.VideoPressIframeApi)return;const o=e.querySelector('script[type="application/json"]');if(!o)return;let r;try{r=JSON.parse(o.innerHTML)}catch(e){return void console.error(e)}o.remove();let n=!1;const s=window.VideoPressIframeApi(t,()=>{s.status.onPlayerStatusChanged((e,t)=>{"ready"===e&&"playing"===t&&(s.controls.pause(),s.controls.seek(r.previewAtTime),s.customize?.set({bigPlayButton:!0,playPauseAnimation:!1,controlBar:!1,shareButton:!1,posterImage:!0}))}),s.status.onTimeUpdate(e=>{if(n)return;const t=1e3*e,o=r.previewAtTime,a=o+r.previewLoopDuration;(t<o||t>a)&&s.controls.seek(o)})}),a=e.querySelector(".jetpack-videopress-player__overlay");a&&(r.showControls&&a.addEventListener("click",()=>{n=!0,a.remove(),s.customize?.set({bigPlayButton:!1,playPauseAnimation:!0,controlBar:!0,shareButton:!0}),s.controls.seek(0)}),a.addEventListener("mouseenter",()=>{s.customize?.set({playPauseAnimation:!1,posterImage:!1}),s.controls.play()}),a.addEventListener("mouseleave",()=>{s.customize?.set({playPauseAnimation:!1,posterImage:!0}),s.controls.pause()}))})}()})})();
|
||||
+1
@@ -0,0 +1 @@
|
||||
.wp-block-jetpack-videopress{position:relative}.wp-block-jetpack-videopress.jetpack-videopress-player.alignleft~.wp-block-jetpack-videopress.jetpack-videopress-player,.wp-block-jetpack-videopress.jetpack-videopress-player.alignright~.wp-block-jetpack-videopress.jetpack-videopress-player{clear:both}.wp-block-jetpack-videopress figcaption{color:#555;font-size:13px;margin-bottom:1em;margin-top:.5em;text-align:center}.is-dark-theme .wp-block-jetpack-videopress figcaption{color:hsla(0,0%,100%,.65)}.wp-block-jetpack-videopress .jetpack-videopress-player__wrapper{line-height:0;position:relative}.wp-block-jetpack-videopress .jetpack-videopress-player__overlay{cursor:pointer;height:100%;opacity:1;position:absolute;right:0;top:0;transition:opacity .3s ease-in-out;width:100%}.wp-block-jetpack-videopress .jetpack-videopress-player__overlay:hover{opacity:0}.wp-block-jetpack-videopress.wp-block-jetpack-videopress--has-max-width{margin:auto}.is-layout-flex .wp-block-jetpack-videopress.wp-block-jetpack-videopress--has-max-width{margin:0}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"beta": [ "videopress-video-chapters-plugin", "videopress/video-chapters" ]
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-polyfill'), 'version' => '8bbbb4cb229ca3eae260');
|
||||
+1
@@ -0,0 +1 @@
|
||||
.extended-block-warning{margin-bottom:8px}.extended-block-warning .block-editor-warning__message{width:100%}.extended-block-warning .block-editor-warning__actions{flex-wrap:wrap}.wp-extended-block-wrapper{position:relative}.wp-extended-block-wrapper.is-disabled{opacity:.5}.wp-extended-block-wrapper .videoplayer-play{background-color:transparent;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMiIgaGVpZ2h0PSIyMiIgZmlsbD0ibm9uZSIgdmlld0JveD0iMCAwIDIyIDIyIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNi4yNTcgMS4wNzVhLjQ4LjQ4IDAgMCAwLS41MDMtLjAwNy41My41MyAwIDAgMC0uMjU0LjQ1OHYxOC45NDhjMCAuMTkuMDk3LjM2NC4yNTQuNDU4LjE1Ni4wOTMuMzQ5LjA5LjUwMy0uMDA3bDE1LTkuNDc0QS41My41MyAwIDAgMCAyMS41IDExYS41My41MyAwIDAgMC0uMjQzLS40NTF6Ii8+PC9zdmc+);background-size:cover;border:none;display:block;height:72px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);transition:none;width:72px}.wp-extended-block-wrapper img{display:block;margin:0 auto;max-width:650px;-o-object-fit:cover;object-fit:cover;width:100%}.wp-extended-block-wrapper .extended-block-player__overlay{background-color:transparent;height:100%;left:0;position:absolute;top:0;width:100%;z-index:2}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
.extended-block-warning{margin-bottom:8px}.extended-block-warning .block-editor-warning__message{width:100%}.extended-block-warning .block-editor-warning__actions{flex-wrap:wrap}.wp-extended-block-wrapper{position:relative}.wp-extended-block-wrapper.is-disabled{opacity:.5}.wp-extended-block-wrapper .videoplayer-play{background-color:transparent;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMiIgaGVpZ2h0PSIyMiIgZmlsbD0ibm9uZSIgdmlld0JveD0iMCAwIDIyIDIyIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNi4yNTcgMS4wNzVhLjQ4LjQ4IDAgMCAwLS41MDMtLjAwNy41My41MyAwIDAgMC0uMjU0LjQ1OHYxOC45NDhjMCAuMTkuMDk3LjM2NC4yNTQuNDU4LjE1Ni4wOTMuMzQ5LjA5LjUwMy0uMDA3bDE1LTkuNDc0QS41My41MyAwIDAgMCAyMS41IDExYS41My41MyAwIDAgMC0uMjQzLS40NTF6Ii8+PC9zdmc+);background-size:cover;border:none;display:block;height:72px;position:absolute;right:50%;top:50%;transform:translate(50%,-50%);transition:none;width:72px}.wp-extended-block-wrapper img{display:block;margin:0 auto;max-width:650px;-o-object-fit:cover;object-fit:cover;width:100%}.wp-extended-block-wrapper .extended-block-player__overlay{background-color:transparent;height:100%;position:absolute;right:0;top:0;width:100%;z-index:2}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Main entry point for auto-generated asset registration.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
// Load script module registration.
|
||||
$modules_file = __DIR__ . '/modules.php';
|
||||
if ( file_exists( $modules_file ) ) {
|
||||
require_once $modules_file;
|
||||
}
|
||||
|
||||
// Load script registration.
|
||||
$scripts_file = __DIR__ . '/scripts.php';
|
||||
if ( file_exists( $scripts_file ) ) {
|
||||
require_once $scripts_file;
|
||||
}
|
||||
|
||||
// Load style registration.
|
||||
$styles_file = __DIR__ . '/styles.php';
|
||||
if ( file_exists( $styles_file ) ) {
|
||||
require_once $styles_file;
|
||||
}
|
||||
|
||||
// Load routes registration.
|
||||
$routes_file = __DIR__ . '/routes.php';
|
||||
if ( file_exists( $routes_file ) ) {
|
||||
require_once $routes_file;
|
||||
}
|
||||
|
||||
// Load widgets registration.
|
||||
$widgets_file = __DIR__ . '/widgets.php';
|
||||
if ( file_exists( $widgets_file ) ) {
|
||||
require_once $widgets_file;
|
||||
}
|
||||
|
||||
// Load pages registration.
|
||||
$pages_file = __DIR__ . '/pages.php';
|
||||
if ( file_exists( $pages_file ) ) {
|
||||
require_once $pages_file;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin constants - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* Returns an array of constants for use in other templates.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
return array(
|
||||
'version' => '0.38.2',
|
||||
'build_url' => plugin_dir_url( __FILE__ ),
|
||||
);
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '282f1be8e4fc8d45ff1b');
|
||||
+1
@@ -0,0 +1 @@
|
||||
body.jetpack_page_jetpack-videopress{--vp-page-bg-canvas:#fcfcfc;--font-headline-medium:48px;--font-headline-small:36px;--font-title-medium:24px;--font-body-small:14px;--font-body-extra-small:14px;--spacing-base:8px}body.jetpack_page_jetpack-videopress #wpcontent{padding-left:0}body.jetpack_page_jetpack-videopress #screen-meta-links,body.jetpack_page_jetpack-videopress #wpfooter{display:none}body.jetpack_page_jetpack-videopress #wpbody-content{bottom:0;box-sizing:border-box;display:flex;flex-direction:column;left:160px;overflow:hidden;padding-bottom:0;position:fixed;right:0;top:32px;width:auto}body.jetpack_page_jetpack-videopress.folded #wpbody-content{left:36px}@media(max-width:960px){body.jetpack_page_jetpack-videopress.auto-fold #wpbody-content{left:36px}}@media(min-width:961px){body.jetpack_page_jetpack-videopress.is-nav-unification:not(.folded) #wpbody-content{left:272px}}body.jetpack_page_jetpack-videopress #wpbody-content :has(.jp-admin-page):not(:has(.boot-layout__stage)){display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}body.jetpack_page_jetpack-videopress .jp-admin-page{display:flex;flex:1 1 auto;flex-direction:column;margin-left:0;min-height:0;min-width:0;overflow:visible}body.jetpack_page_jetpack-videopress .jp-admin-page__page{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}body.jetpack_page_jetpack-videopress .jp-admin-page__page>:first-child{flex-shrink:0}body.jetpack_page_jetpack-videopress .jp-admin-page__page:has(.jp-admin-page-tabs)>:first-child{border-bottom:none;padding-bottom:0}body.jetpack_page_jetpack-videopress .jp-admin-page__page>:not(:first-child):not(.jetpack-footer){display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0;overflow:auto}body.jetpack_page_jetpack-videopress .jp-admin-page__page>:not(:first-child):not(.jetpack-footer)>*{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}body.jetpack_page_jetpack-videopress .jetpack-footer{flex-shrink:0}body.jetpack_page_jetpack-videopress .jp-admin-page-tabs{background:var(--wpds-color-bg-surface-neutral-strong);border-bottom:var(--wpds-border-width-xs) solid var(--wpds-color-stroke-surface-neutral-weak);padding-inline:8px;position:sticky;top:0;z-index:10}body.jetpack_page_jetpack-videopress .jp-admin-page-tabs--minimal{padding-inline:24px}body.jetpack_page_jetpack-videopress .jp-admin-page-tabs [role=tab]{font-size:13px}@media(max-width:782px){body.jetpack_page_jetpack-videopress #wpbody-content,body.jetpack_page_jetpack-videopress.auto-fold #wpbody-content,body.jetpack_page_jetpack-videopress.folded #wpbody-content{left:0;top:46px}body.jetpack_page_jetpack-videopress .jp-admin-page{margin-left:0}}body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] li{margin:0}body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] a{text-decoration:none}body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] a:focus-visible,body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] a:hover{text-decoration:underline}body.jetpack_page_jetpack-videopress,body.jetpack_page_jetpack-videopress .jp-admin-page,body.jetpack_page_jetpack-videopress .jp-admin-page__page{background-color:var(--vp-page-bg-canvas)}
|
||||
+1
@@ -0,0 +1 @@
|
||||
body.jetpack_page_jetpack-videopress{--vp-page-bg-canvas:#fcfcfc;--font-headline-medium:48px;--font-headline-small:36px;--font-title-medium:24px;--font-body-small:14px;--font-body-extra-small:14px;--spacing-base:8px}body.jetpack_page_jetpack-videopress #wpcontent{padding-right:0}body.jetpack_page_jetpack-videopress #screen-meta-links,body.jetpack_page_jetpack-videopress #wpfooter{display:none}body.jetpack_page_jetpack-videopress #wpbody-content{bottom:0;box-sizing:border-box;display:flex;flex-direction:column;left:0;overflow:hidden;padding-bottom:0;position:fixed;right:160px;top:32px;width:auto}body.jetpack_page_jetpack-videopress.folded #wpbody-content{right:36px}@media(max-width:960px){body.jetpack_page_jetpack-videopress.auto-fold #wpbody-content{right:36px}}@media(min-width:961px){body.jetpack_page_jetpack-videopress.is-nav-unification:not(.folded) #wpbody-content{right:272px}}body.jetpack_page_jetpack-videopress #wpbody-content :has(.jp-admin-page):not(:has(.boot-layout__stage)){display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}body.jetpack_page_jetpack-videopress .jp-admin-page{display:flex;flex:1 1 auto;flex-direction:column;margin-right:0;min-height:0;min-width:0;overflow:visible}body.jetpack_page_jetpack-videopress .jp-admin-page__page{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}body.jetpack_page_jetpack-videopress .jp-admin-page__page>:first-child{flex-shrink:0}body.jetpack_page_jetpack-videopress .jp-admin-page__page:has(.jp-admin-page-tabs)>:first-child{border-bottom:none;padding-bottom:0}body.jetpack_page_jetpack-videopress .jp-admin-page__page>:not(:first-child):not(.jetpack-footer){display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0;overflow:auto}body.jetpack_page_jetpack-videopress .jp-admin-page__page>:not(:first-child):not(.jetpack-footer)>*{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;min-width:0}body.jetpack_page_jetpack-videopress .jetpack-footer{flex-shrink:0}body.jetpack_page_jetpack-videopress .jp-admin-page-tabs{background:var(--wpds-color-bg-surface-neutral-strong);border-bottom:var(--wpds-border-width-xs) solid var(--wpds-color-stroke-surface-neutral-weak);padding-inline:8px;position:sticky;top:0;z-index:10}body.jetpack_page_jetpack-videopress .jp-admin-page-tabs--minimal{padding-inline:24px}body.jetpack_page_jetpack-videopress .jp-admin-page-tabs [role=tab]{font-size:13px}@media(max-width:782px){body.jetpack_page_jetpack-videopress #wpbody-content,body.jetpack_page_jetpack-videopress.auto-fold #wpbody-content,body.jetpack_page_jetpack-videopress.folded #wpbody-content{right:0;top:46px}body.jetpack_page_jetpack-videopress .jp-admin-page{margin-right:0}}body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] li{margin:0}body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] a{text-decoration:none}body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] a:focus-visible,body.jetpack_page_jetpack-videopress nav[aria-label=Breadcrumbs] a:hover{text-decoration:underline}body.jetpack_page_jetpack-videopress,body.jetpack_page_jetpack-videopress .jp-admin-page,body.jetpack_page_jetpack-videopress .jp-admin-page__page{background-color:var(--vp-page-bg-canvas)}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('divi-vendor-wp-hooks', 'divi-vendor-wp-i18n', 'react', 'react-jsx-runtime'), 'version' => 'fe8c340990d821843340');
|
||||
+2
File diff suppressed because one or more lines are too long
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"advanced": {
|
||||
"admin_label": "module.meta.adminLabel",
|
||||
"animation": "module.decoration.animation",
|
||||
"background": "module.decoration.background",
|
||||
"disabled_on": "module.decoration.disabledOn",
|
||||
"module": "module.advanced.htmlAttributes",
|
||||
"overflow": "module.decoration.overflow",
|
||||
"position_fields": "module.decoration.position",
|
||||
"scroll": "module.decoration.scroll",
|
||||
"sticky": "module.decoration.sticky",
|
||||
"transform": "module.decoration.transform",
|
||||
"transition": "module.decoration.transition",
|
||||
"z_index": "module.decoration.zIndex",
|
||||
"margin_padding": "module.decoration.spacing",
|
||||
"max_width": "module.decoration.sizing",
|
||||
"height": "module.decoration.sizing",
|
||||
"box_shadow": { "default": "module.decoration.boxShadow" },
|
||||
"borders": { "default": "module.decoration.border" },
|
||||
"filters": { "default": "module.decoration.filters" }
|
||||
},
|
||||
"css": {
|
||||
"after": "css.*.after",
|
||||
"before": "css.*.before",
|
||||
"main_element": "css.*.mainElement"
|
||||
},
|
||||
"module": {
|
||||
"guid": "guid.innerContent.*"
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"name": "jetpack/videopress",
|
||||
"d4Shortcode": "divi_videopress",
|
||||
"title": "VideoPress",
|
||||
"titles": "VideoPress",
|
||||
"moduleIcon": "jetpack/videopress-logo",
|
||||
"moduleClassName": "et_pb_videopress",
|
||||
"moduleOrderClassName": "et_pb_videopress",
|
||||
"category": "module",
|
||||
"attributes": {
|
||||
"module": {
|
||||
"type": "object",
|
||||
"selector": "{{selector}}",
|
||||
"styleProps": {
|
||||
"sizing": {
|
||||
"propertySelectors": {
|
||||
"desktop": {
|
||||
"value": {
|
||||
"margin-left": "{{selector}}.et_pb_module",
|
||||
"margin-right": "{{selector}}.et_pb_module"
|
||||
}
|
||||
}
|
||||
},
|
||||
"important": {
|
||||
"desktop": {
|
||||
"value": {
|
||||
"margin-left": true,
|
||||
"margin-right": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"meta": {
|
||||
"adminLabel": {}
|
||||
},
|
||||
"advanced": {
|
||||
"htmlAttributes": {}
|
||||
},
|
||||
"decoration": {
|
||||
"background": {},
|
||||
"sizing": {},
|
||||
"spacing": {},
|
||||
"border": {},
|
||||
"boxShadow": {},
|
||||
"filters": {},
|
||||
"transform": {},
|
||||
"animation": {},
|
||||
"overflow": {},
|
||||
"disabledOn": {},
|
||||
"transition": {},
|
||||
"position": {},
|
||||
"zIndex": {},
|
||||
"scroll": {},
|
||||
"sticky": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"guid": {
|
||||
"type": "object",
|
||||
"selector": "{{selector}} .vidi-videopress-wrapper",
|
||||
"settings": {
|
||||
"innerContent": {
|
||||
"groupType": "group-item",
|
||||
"item": {
|
||||
"groupName": "mainContent",
|
||||
"attrName": "guid.innerContent",
|
||||
"priority": 10,
|
||||
"render": true,
|
||||
"label": "URL or Video ID",
|
||||
"description": "Paste a VideoPress URL or Video ID.",
|
||||
"features": {
|
||||
"sticky": false,
|
||||
"dynamicContent": false,
|
||||
"responsive": false
|
||||
},
|
||||
"component": {
|
||||
"name": "divi/text",
|
||||
"type": "field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoplay": {
|
||||
"type": "object",
|
||||
"settings": {
|
||||
"innerContent": {
|
||||
"groupType": "group-item",
|
||||
"item": {
|
||||
"groupSlug": "playerOptions",
|
||||
"attrName": "autoplay.innerContent",
|
||||
"priority": 10,
|
||||
"render": true,
|
||||
"label": "Autoplay",
|
||||
"description": "Start playing the video automatically.",
|
||||
"features": {
|
||||
"sticky": false,
|
||||
"responsive": false
|
||||
},
|
||||
"component": {
|
||||
"name": "divi/toggle",
|
||||
"type": "field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loop": {
|
||||
"type": "object",
|
||||
"settings": {
|
||||
"innerContent": {
|
||||
"groupType": "group-item",
|
||||
"item": {
|
||||
"groupSlug": "playerOptions",
|
||||
"attrName": "loop.innerContent",
|
||||
"priority": 20,
|
||||
"render": true,
|
||||
"label": "Loop",
|
||||
"description": "Restart the video when it reaches the end.",
|
||||
"features": {
|
||||
"sticky": false,
|
||||
"responsive": false
|
||||
},
|
||||
"component": {
|
||||
"name": "divi/toggle",
|
||||
"type": "field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"muted": {
|
||||
"type": "object",
|
||||
"settings": {
|
||||
"innerContent": {
|
||||
"groupType": "group-item",
|
||||
"item": {
|
||||
"groupSlug": "playerOptions",
|
||||
"attrName": "muted.innerContent",
|
||||
"priority": 30,
|
||||
"render": true,
|
||||
"label": "Muted",
|
||||
"description": "Start the video without sound.",
|
||||
"features": {
|
||||
"sticky": false,
|
||||
"responsive": false
|
||||
},
|
||||
"component": {
|
||||
"name": "divi/toggle",
|
||||
"type": "field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
"type": "object",
|
||||
"settings": {
|
||||
"innerContent": {
|
||||
"groupType": "group-item",
|
||||
"item": {
|
||||
"groupSlug": "playerOptions",
|
||||
"attrName": "controls.innerContent",
|
||||
"priority": 40,
|
||||
"render": true,
|
||||
"label": "Player Controls",
|
||||
"description": "Show the playback controls.",
|
||||
"features": {
|
||||
"sticky": false,
|
||||
"responsive": false
|
||||
},
|
||||
"component": {
|
||||
"name": "divi/toggle",
|
||||
"type": "field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"playsinline": {
|
||||
"type": "object",
|
||||
"settings": {
|
||||
"innerContent": {
|
||||
"groupType": "group-item",
|
||||
"item": {
|
||||
"groupSlug": "playerOptions",
|
||||
"attrName": "playsinline.innerContent",
|
||||
"priority": 50,
|
||||
"render": true,
|
||||
"label": "Play Inline",
|
||||
"description": "Play the video inline on mobile instead of fullscreen.",
|
||||
"features": {
|
||||
"sticky": false,
|
||||
"responsive": false
|
||||
},
|
||||
"component": {
|
||||
"name": "divi/toggle",
|
||||
"type": "field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"customCssFields": {
|
||||
"videoPlayer": {
|
||||
"subName": "videoPlayer",
|
||||
"selectorSuffix": " .vidi-videopress-wrapper"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"content": "auto",
|
||||
"design": "auto",
|
||||
"advanced": "auto",
|
||||
"groups": {
|
||||
"playerOptions": {
|
||||
"panel": "content",
|
||||
"priority": 20,
|
||||
"groupName": "playerOptions",
|
||||
"component": {
|
||||
"name": "divi/composite",
|
||||
"props": {
|
||||
"groupLabel": "Player"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('jquery', 'react', 'react-jsx-runtime'), 'version' => '2527ba26443b669a5a21');
|
||||
+1
@@ -0,0 +1 @@
|
||||
.vidi-videopress-wrapper{height:0;padding-bottom:56.25%;position:relative;width:100%}.vidi-videopress-wrapper iframe{height:100%;left:0;position:absolute;top:0;width:100%}
|
||||
+1
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";var e={6110(){},2312(e,r,s){s.d(r,{A:()=>t});const t=[s(8933).A]},8933(e,r,s){s.d(r,{A:()=>n});var t=s(1609),o=(s(6110),s(790));const i=/^(?:(?:http(?:s)?:\/\/)?(?:www\.)?video(?:\.word)?press\.com\/(?:v|embed)\/)?([a-zA-Z\d]+)(?:.*)?/i;class d extends t.Component{static slug="divi_videopress";render(){let e=this.props.guid;if(!e)return null;const r=e.match(i);if(!r||!r[1])return null;e=r[1];const s="https://videopress.com/embed/"+e+"?autoPlay=0&permalink=0&loop=0&embedder=divi-builder",t=`VideoPress video ${e}`;return(0,o.jsxs)("div",{className:"vidi-videopress-wrapper",children:[(0,o.jsx)("iframe",{title:t,src:s,width:"100%",height:"100%"}),(0,o.jsx)("script",{src:"https://en.wordpress.com/wp-content/plugins/video/assets/js/next/videopress-iframe.js?m=1658739239"})]})}}const n=d},1609(e){e.exports=window.React},790(e){e.exports=window.ReactJSXRuntime},428(e){e.exports=window.jQuery}},r={};function s(t){var o=r[t];if(void 0!==o)return o.exports;var i=r[t]={exports:{}};return e[t](i,i.exports,s),i.exports}s.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return s.d(r,{a:r}),r},s.d=(e,r)=>{for(var t in r)s.o(r,t)&&!s.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},s.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r);var t=s(428),o=s.n(t),i=s(2312);o()(window).on("et_builder_api_ready",(e,r)=>{r.registerModules(i.A)})})();
|
||||
+1
@@ -0,0 +1 @@
|
||||
.vidi-videopress-wrapper{height:0;padding-bottom:56.25%;position:relative;width:100%}.vidi-videopress-wrapper iframe{height:100%;position:absolute;right:0;top:0;width:100%}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
+17
@@ -0,0 +1,17 @@
|
||||
<svg width="380" height="183" viewBox="0 0 380 183" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.75" filter="url(#filter0_f_5953_11924)">
|
||||
<path d="M360 -60C360 7.93102 304.931 63 237 63C169.069 63 114 7.93102 114 -60C114 -127.931 169.069 -183 237 -183C304.931 -183 360 -127.931 360 -60Z" fill="url(#paint0_linear_5953_11924)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_5953_11924" x="-6" y="-303" width="486" height="486" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="60" result="effect1_foregroundBlur_5953_11924"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_5953_11924" x1="360" y1="-60" x2="114" y2="-60" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#48FF50"/>
|
||||
<stop offset="0.471846" stop-color="#108642"/>
|
||||
<stop offset="1" stop-color="#2E38FA"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 990 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('wp-polyfill'), 'version' => '687ee611e76167057815');
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('wp-polyfill'), 'version' => 'd641d321fde6d46173aa');
|
||||
+1
File diff suppressed because one or more lines are too long
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Script module registration - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register all script modules.
|
||||
*/
|
||||
function jetpack_videopress_register_script_modules() {
|
||||
// Ensure this only runs once. wp_default_scripts can fire multiple times,
|
||||
// and each wp_deregister_script_module() call also dequeues the module.
|
||||
// If a module was enqueued between calls, repeated deregister/register
|
||||
// cycles would lose the enqueue state.
|
||||
static $already_registered = false;
|
||||
if ( $already_registered ) {
|
||||
return;
|
||||
}
|
||||
$already_registered = true;
|
||||
|
||||
// Load build constants
|
||||
$constants_file = __DIR__ . '/constants.php';
|
||||
if ( ! file_exists( $constants_file ) ) {
|
||||
return;
|
||||
}
|
||||
$build_constants = require $constants_file;
|
||||
$modules_dir = __DIR__ . '/modules';
|
||||
$modules_file = $modules_dir . '/registry.php';
|
||||
|
||||
if ( ! file_exists( $modules_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$modules = require $modules_file;
|
||||
$base_url = $build_constants['build_url'] . 'modules/';
|
||||
|
||||
foreach ( $modules as $module ) {
|
||||
// WASM-only modules (e.g., vips worker) only have .min.js; use it even when SCRIPT_DEBUG is `true`.
|
||||
$extension = '.min.js';
|
||||
|
||||
$asset_path = $modules_dir . '/' . $module['asset'];
|
||||
$asset = file_exists( $asset_path ) ? require $asset_path : array();
|
||||
|
||||
// Deregister first to override any previously registered version
|
||||
// (e.g., Core's default modules when running as a plugin).
|
||||
wp_deregister_script_module( $module['id'] );
|
||||
|
||||
wp_register_script_module(
|
||||
$module['id'],
|
||||
$base_url . $module['path'] . $extension,
|
||||
$asset['module_dependencies'] ?? array(),
|
||||
$asset['version'] ?? false,
|
||||
array(
|
||||
'fetchpriority' => 'low',
|
||||
'in_footer' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'wp_default_scripts', 'jetpack_videopress_register_script_modules' );
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/lazy-editor', 'import' => 'dynamic'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '188faf6c64fdd1b4');
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Script module registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Pages loader - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
foreach ( [
|
||||
__DIR__ . '/pages/jetpack-videopress-dashboard/page.php',
|
||||
__DIR__ . '/pages/jetpack-videopress-dashboard/page-wp-admin.php',
|
||||
] as $file ) {
|
||||
if ( file_exists( $file ) ) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
// Empty module loader for page dependencies
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
/**
|
||||
* Page: jetpack-videopress-dashboard (wp-admin integrated)
|
||||
* Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* This version integrates with the standard WordPress admin interface,
|
||||
* keeping the wp-admin sidebar and scripts/styles intact.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
// Global storage for jetpack-videopress-dashboard routes and menu items
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_routes, $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_menu_items;
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_wp_admin_routes = array();
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_wp_admin_menu_items = array();
|
||||
|
||||
/**
|
||||
* Register a route for the jetpack-videopress-dashboard-wp-admin page.
|
||||
*
|
||||
* @param string $path Route path (e.g., '/types/$type/edit/$id').
|
||||
* @param string|null $content_module Script module ID for content (stage/inspector).
|
||||
* @param string|null $route_module Script module ID for route lifecycle hooks.
|
||||
*/
|
||||
function jetpack_videopress_register_jetpack_videopress_dashboard_wp_admin_route( $path, $content_module = null, $route_module = null ) {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_routes;
|
||||
|
||||
$route = array( 'path' => $path );
|
||||
if ( ! empty( $content_module ) ) {
|
||||
$route['content_module'] = $content_module;
|
||||
}
|
||||
if ( ! empty( $route_module ) ) {
|
||||
$route['route_module'] = $route_module;
|
||||
}
|
||||
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_wp_admin_routes[] = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a menu item for the jetpack-videopress-dashboard-wp-admin page.
|
||||
* Note: Menu items are registered but not displayed in single-page mode.
|
||||
*
|
||||
* @param string $id Menu item ID.
|
||||
* @param string $label Display label.
|
||||
* @param string $to Route path to navigate to.
|
||||
* @param string $parent_id Optional. Parent menu item ID.
|
||||
*/
|
||||
function jetpack_videopress_register_jetpack_videopress_dashboard_wp_admin_menu_item( $id, $label, $to, $parent_id = '' ) {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_menu_items;
|
||||
|
||||
$menu_item = array(
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'to' => $to,
|
||||
);
|
||||
|
||||
if ( ! empty( $parent_id ) ) {
|
||||
$menu_item['parent'] = $parent_id;
|
||||
}
|
||||
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_wp_admin_menu_items[] = $menu_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered routes for the jetpack-videopress-dashboard-wp-admin page.
|
||||
*
|
||||
* @return array Array of route objects.
|
||||
*/
|
||||
function jetpack_videopress_get_jetpack_videopress_dashboard_wp_admin_routes() {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_routes;
|
||||
return $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_routes ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered menu items for the jetpack-videopress-dashboard-wp-admin page.
|
||||
*
|
||||
* @return array Array of menu item objects.
|
||||
*/
|
||||
function jetpack_videopress_get_jetpack_videopress_dashboard_wp_admin_menu_items() {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_menu_items;
|
||||
return $jetpack_videopress_jetpack_videopress_dashboard_wp_admin_menu_items ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload REST API data for the jetpack-videopress-dashboard-wp-admin page.
|
||||
* Automatically called during page rendering.
|
||||
*/
|
||||
function jetpack_videopress_jetpack_videopress_dashboard_wp_admin_preload_data() {
|
||||
// Define paths to preload - same for all pages
|
||||
// Please also change packages/core-data/src/entities.js when changing this.
|
||||
$preload_paths = array(
|
||||
'/?_fields=description,gmt_offset,home,image_sizes,image_size_threshold,name,site_icon,site_icon_url,site_logo,timezone_string,url,page_for_posts,page_on_front,show_on_front',
|
||||
array( '/wp/v2/settings', 'OPTIONS' ),
|
||||
);
|
||||
|
||||
// Use rest_preload_api_request to gather the preloaded data
|
||||
$preload_data = array_reduce(
|
||||
$preload_paths,
|
||||
'rest_preload_api_request',
|
||||
array()
|
||||
);
|
||||
|
||||
// Register the preloading middleware with wp-api-fetch
|
||||
wp_add_inline_script(
|
||||
'wp-api-fetch',
|
||||
sprintf(
|
||||
'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
|
||||
wp_json_encode( $preload_data )
|
||||
),
|
||||
'after'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles for the jetpack-videopress-dashboard-wp-admin page.
|
||||
* Hooked to admin_enqueue_scripts.
|
||||
*
|
||||
* @param string $hook_suffix The current admin page.
|
||||
*/
|
||||
function jetpack_videopress_jetpack_videopress_dashboard_wp_admin_enqueue_scripts( $hook_suffix ) {
|
||||
// Check all possible ways this page can be accessed:
|
||||
// 1. Menu page via admin.php?page=jetpack-videopress-dashboard-wp-admin (plugin)
|
||||
// 2. Direct file via jetpack-videopress-dashboard.php (Core) - screen ID will be 'jetpack-videopress-dashboard'
|
||||
$current_screen = get_current_screen();
|
||||
$is_our_page = (
|
||||
( isset( $_GET['page'] ) && 'jetpack-videopress-dashboard-wp-admin' === $_GET['page'] ) || // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
( $current_screen && 'jetpack-videopress-dashboard' === $current_screen->id )
|
||||
);
|
||||
|
||||
if ( ! $is_our_page ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/../../constants.php';
|
||||
|
||||
/**
|
||||
* Fires when the jetpack-videopress-dashboard admin page is initialized so extensions can register routes and menu items.
|
||||
*/
|
||||
do_action( 'jetpack-videopress-dashboard-wp-admin_init' );
|
||||
|
||||
// Preload REST API data
|
||||
jetpack_videopress_jetpack_videopress_dashboard_wp_admin_preload_data();
|
||||
|
||||
// Get all registered routes
|
||||
$routes = jetpack_videopress_get_jetpack_videopress_dashboard_wp_admin_routes();
|
||||
|
||||
// Get boot module asset file for dependencies
|
||||
$asset_file = __DIR__ . '/../../modules/boot/index.min.asset.php';
|
||||
if ( file_exists( $asset_file ) ) {
|
||||
$asset = require $asset_file;
|
||||
|
||||
// This script serves two purposes:
|
||||
// 1. It ensures all the globals that are made available to the modules are loaded.
|
||||
// 2. It initializes the boot module as an inline script.
|
||||
wp_register_script( 'jetpack-videopress-dashboard-wp-admin-prerequisites', '', $asset['dependencies'], $asset['version'], true );
|
||||
|
||||
/*
|
||||
* Add inline script to initialize the app using initSinglePage (no menuItems).
|
||||
* The dynamic import is deferred until DOMContentLoaded so that all classic
|
||||
* script dependencies of @wordpress/boot (wp-private-apis, wp-components,
|
||||
* wp-theme, etc.) have finished parsing and executing before the boot module
|
||||
* evaluates. Otherwise, a modulepreloaded @wordpress/boot can win the race
|
||||
* against the classic-script-printing pass on fast CDN-fronted hosts in
|
||||
* Chrome, evaluating before wp.theme.privateApis is defined and throwing
|
||||
* "Cannot unlock an undefined object". See <https://core.trac.wordpress.org/ticket/65103>.
|
||||
*/
|
||||
$init_js_function = <<<'JS'
|
||||
( mountId, routes ) => {
|
||||
const run = async () => {
|
||||
const mod = await import( "@wordpress/boot" );
|
||||
mod.initSinglePage( { mountId, routes } );
|
||||
};
|
||||
if ( document.readyState === "loading" ) {
|
||||
document.addEventListener( "DOMContentLoaded", run );
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
JS;
|
||||
wp_add_inline_script(
|
||||
'jetpack-videopress-dashboard-wp-admin-prerequisites',
|
||||
sprintf(
|
||||
'( %s )( %s, %s );',
|
||||
$init_js_function,
|
||||
wp_json_encode( 'jetpack-videopress-dashboard-wp-admin-app', JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
|
||||
)
|
||||
);
|
||||
|
||||
// Register prerequisites style by filtering script dependencies to find registered styles
|
||||
$style_dependencies = array_filter(
|
||||
$asset['dependencies'],
|
||||
function ( $handle ) {
|
||||
return wp_style_is( $handle, 'registered' );
|
||||
}
|
||||
);
|
||||
wp_register_style( 'jetpack-videopress-dashboard-wp-admin-prerequisites', false, $style_dependencies, $asset['version'] );
|
||||
|
||||
// Build dependencies for jetpack-videopress-dashboard-wp-admin module
|
||||
$boot_dependencies = array(
|
||||
array(
|
||||
'import' => 'static',
|
||||
'id' => '@wordpress/boot',
|
||||
),
|
||||
);
|
||||
|
||||
// Add all registered routes as dependencies
|
||||
foreach ( $routes as $route ) {
|
||||
if ( isset( $route['route_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'static',
|
||||
'id' => $route['route_module'],
|
||||
);
|
||||
}
|
||||
if ( isset( $route['content_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'dynamic',
|
||||
'id' => $route['content_module'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the boot script-module dependencies for the
|
||||
* jetpack-videopress-dashboard-wp-admin page.
|
||||
*
|
||||
* Surfaces extending this page can append entries to the boot
|
||||
* dependency list. Each entry is an array with 'import' (string
|
||||
* 'static' or 'dynamic') and 'id' (script-module handle) keys.
|
||||
*
|
||||
* @param array $boot_dependencies Boot dependencies for the page.
|
||||
*/
|
||||
$boot_dependencies = apply_filters(
|
||||
'jetpack-videopress-dashboard-wp-admin_boot_dependencies',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Dummy script module to ensure dependencies are loaded
|
||||
wp_register_script_module(
|
||||
'jetpack-videopress-dashboard-wp-admin',
|
||||
$build_constants['build_url'] . 'pages/jetpack-videopress-dashboard/loader.js',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Enqueue the boot scripts and styles
|
||||
wp_enqueue_script( 'jetpack-videopress-dashboard-wp-admin-prerequisites' );
|
||||
wp_enqueue_script_module( 'jetpack-videopress-dashboard-wp-admin' );
|
||||
wp_enqueue_style( 'jetpack-videopress-dashboard-wp-admin-prerequisites' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the jetpack-videopress-dashboard-wp-admin page.
|
||||
* Call this function from add_menu_page or add_submenu_page.
|
||||
* This renders within the normal WordPress admin interface.
|
||||
*/
|
||||
function jetpack_videopress_jetpack_videopress_dashboard_wp_admin_render_page() {
|
||||
?>
|
||||
<style>
|
||||
/* Critical styles to prevent layout shifts - inlined for immediate application */
|
||||
|
||||
#wpwrap {
|
||||
overflow-y: auto;
|
||||
}
|
||||
body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* Reset wp-admin padding */
|
||||
#wpcontent {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
#wpbody-content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Hide legacy admin elements */
|
||||
#wpbody-content > div:not(.boot-layout-container):not(#screen-meta) {
|
||||
display: none;
|
||||
}
|
||||
#wpfooter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Accessibility regions */
|
||||
.a11y-speak-region {
|
||||
inset-inline-start: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
/* Admin menu indicators */
|
||||
ul#adminmenu a.wp-has-current-submenu::after,
|
||||
ul#adminmenu > li.current > a.current::after {
|
||||
border-inline-end-color: #fff;
|
||||
}
|
||||
|
||||
/* Media frame fix */
|
||||
.media-frame select.attachment-filters:last-of-type {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Responsive overflow fix for #wpwrap */
|
||||
@media (min-width: 782px) {
|
||||
#wpwrap {
|
||||
overflow-y: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="jetpack-videopress-dashboard-wp-admin-app" class="boot-layout-container"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Hook the enqueue function to admin_enqueue_scripts
|
||||
add_action( 'admin_enqueue_scripts', 'jetpack_videopress_jetpack_videopress_dashboard_wp_admin_enqueue_scripts' );
|
||||
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
/**
|
||||
* Page: jetpack-videopress-dashboard
|
||||
* Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
// Global storage for jetpack-videopress-dashboard routes and menu items
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_routes, $jetpack_videopress_jetpack_videopress_dashboard_menu_items;
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_routes = array();
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_menu_items = array();
|
||||
|
||||
/**
|
||||
* Register a route for the jetpack-videopress-dashboard page.
|
||||
*
|
||||
* @param string $path Route path (e.g., '/types/$type/edit/$id').
|
||||
* @param string|null $content_module Script module ID for content (stage/inspector).
|
||||
* @param string|null $route_module Script module ID for route lifecycle hooks.
|
||||
*/
|
||||
function jetpack_videopress_register_jetpack_videopress_dashboard_route( $path, $content_module = null, $route_module = null ) {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_routes;
|
||||
|
||||
$route = array( 'path' => $path );
|
||||
if ( ! empty( $content_module ) ) {
|
||||
$route['content_module'] = $content_module;
|
||||
}
|
||||
if ( ! empty( $route_module ) ) {
|
||||
$route['route_module'] = $route_module;
|
||||
}
|
||||
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_routes[] = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a menu item for the jetpack-videopress-dashboard page.
|
||||
*
|
||||
* @param string $id Menu item ID.
|
||||
* @param string $label Display label.
|
||||
* @param string $to Route path to navigate to.
|
||||
* @param string $parent_id Optional. Parent menu item ID.
|
||||
* @param string $parent_type Optional. Parent type: 'drilldown' or 'dropdown'.
|
||||
*/
|
||||
function jetpack_videopress_register_jetpack_videopress_dashboard_menu_item( $id, $label, $to, $parent_id = '', $parent_type = '' ) {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_menu_items;
|
||||
|
||||
$menu_item = array(
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'to' => $to,
|
||||
);
|
||||
|
||||
if ( ! empty( $parent_id ) ) {
|
||||
$menu_item['parent'] = $parent_id;
|
||||
}
|
||||
|
||||
if ( ! empty( $parent_type ) && in_array( $parent_type, array( 'drilldown', 'dropdown' ), true ) ) {
|
||||
$menu_item['parent_type'] = $parent_type;
|
||||
}
|
||||
|
||||
$jetpack_videopress_jetpack_videopress_dashboard_menu_items[] = $menu_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered routes for the jetpack-videopress-dashboard page.
|
||||
*
|
||||
* @return array Array of route objects.
|
||||
*/
|
||||
function jetpack_videopress_get_jetpack_videopress_dashboard_routes() {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_routes;
|
||||
return $jetpack_videopress_jetpack_videopress_dashboard_routes ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered menu items for the jetpack-videopress-dashboard page.
|
||||
*
|
||||
* @return array Array of menu item objects.
|
||||
*/
|
||||
function jetpack_videopress_get_jetpack_videopress_dashboard_menu_items() {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_menu_items;
|
||||
return $jetpack_videopress_jetpack_videopress_dashboard_menu_items ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload REST API data for the jetpack-videopress-dashboard page.
|
||||
* Automatically called during page rendering.
|
||||
*/
|
||||
function jetpack_videopress_jetpack_videopress_dashboard_preload_data() {
|
||||
// Define paths to preload - same for all pages
|
||||
// Please also change packages/core-data/src/entities.js when changing this.
|
||||
$preload_paths = array(
|
||||
'/?_fields=description,gmt_offset,home,image_sizes,image_size_threshold,name,site_icon,site_icon_url,site_logo,timezone_string,url,page_for_posts,page_on_front,show_on_front',
|
||||
array( '/wp/v2/settings', 'OPTIONS' ),
|
||||
);
|
||||
|
||||
// Use rest_preload_api_request to gather the preloaded data
|
||||
$preload_data = array_reduce(
|
||||
$preload_paths,
|
||||
'rest_preload_api_request',
|
||||
array()
|
||||
);
|
||||
|
||||
// Register the preloading middleware with wp-api-fetch
|
||||
wp_add_inline_script(
|
||||
'wp-api-fetch',
|
||||
sprintf(
|
||||
'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
|
||||
wp_json_encode( $preload_data )
|
||||
),
|
||||
'after'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the jetpack-videopress-dashboard page.
|
||||
* Call this function from add_menu_page or add_submenu_page.
|
||||
*/
|
||||
function jetpack_videopress_jetpack_videopress_dashboard_render_page() {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/../../constants.php';
|
||||
|
||||
// Set current screen
|
||||
set_current_screen();
|
||||
|
||||
// Remove unwanted deprecated handler
|
||||
remove_action( 'admin_head', 'wp_admin_bar_header' );
|
||||
|
||||
// Remove unwanted scripts and styles that were enqueued during `admin_init`
|
||||
foreach ( wp_scripts()->queue as $script ) {
|
||||
wp_dequeue_script( $script );
|
||||
}
|
||||
foreach ( wp_styles()->queue as $style ) {
|
||||
wp_dequeue_style( $style );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the jetpack-videopress-dashboard page is initialized so extensions can register routes and menu items.
|
||||
*/
|
||||
do_action( 'jetpack-videopress-dashboard_init' );
|
||||
|
||||
// Enqueue command palette assets for boot-based pages
|
||||
if ( function_exists( 'wp_enqueue_command_palette_assets' ) ) {
|
||||
wp_enqueue_command_palette_assets();
|
||||
}
|
||||
|
||||
// Preload REST API data
|
||||
jetpack_videopress_jetpack_videopress_dashboard_preload_data();
|
||||
|
||||
// Get all registered routes and menu items
|
||||
$menu_items = jetpack_videopress_get_jetpack_videopress_dashboard_menu_items();
|
||||
$routes = jetpack_videopress_get_jetpack_videopress_dashboard_routes();
|
||||
|
||||
// Get boot module asset file for dependencies
|
||||
$asset_file = __DIR__ . '/../../modules/boot/index.min.asset.php';
|
||||
if ( file_exists( $asset_file ) ) {
|
||||
$asset = require $asset_file;
|
||||
|
||||
// This script serves two purposes:
|
||||
// 1. It ensures all the globals that are made available to the modules are loaded.
|
||||
// 2. It initializes the boot module as an inline script.
|
||||
wp_register_script( 'jetpack-videopress-dashboard-prerequisites', '', $asset['dependencies'], $asset['version'], true );
|
||||
|
||||
// Add inline script to initialize the app
|
||||
$init_modules = [];
|
||||
wp_add_inline_script(
|
||||
'jetpack-videopress-dashboard-prerequisites',
|
||||
sprintf(
|
||||
'import("@wordpress/boot").then(mod => mod.init({mountId: "%s", menuItems: %s, routes: %s, initModules: %s, dashboardLink: "%s"}));',
|
||||
'jetpack-videopress-dashboard-app',
|
||||
wp_json_encode( $menu_items, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
wp_json_encode( $init_modules, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
esc_url( admin_url( '/' ) )
|
||||
)
|
||||
);
|
||||
|
||||
// Register prerequisites style by filtering script dependencies to find registered styles
|
||||
$style_dependencies = array_filter(
|
||||
$asset['dependencies'],
|
||||
function ( $handle ) {
|
||||
return wp_style_is( $handle, 'registered' );
|
||||
}
|
||||
);
|
||||
wp_register_style( 'jetpack-videopress-dashboard-prerequisites', false, $style_dependencies, $asset['version'] );
|
||||
|
||||
// Build dependencies for jetpack-videopress-dashboard module
|
||||
$boot_dependencies = array(
|
||||
array(
|
||||
'import' => 'static',
|
||||
'id' => '@wordpress/boot',
|
||||
),
|
||||
);
|
||||
|
||||
// Add init modules as static dependencies
|
||||
// No init modules configured
|
||||
|
||||
// Add all registered routes as dependencies
|
||||
foreach ( $routes as $route ) {
|
||||
if ( isset( $route['route_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'static',
|
||||
'id' => $route['route_module'],
|
||||
);
|
||||
}
|
||||
if ( isset( $route['content_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'dynamic',
|
||||
'id' => $route['content_module'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the boot script-module dependencies for the
|
||||
* jetpack-videopress-dashboard page.
|
||||
*
|
||||
* Surfaces extending this page can append entries to the boot
|
||||
* dependency list. Each entry is an array with 'import' (string
|
||||
* 'static' or 'dynamic') and 'id' (script-module handle) keys.
|
||||
*
|
||||
* @param array $boot_dependencies Boot dependencies for the page.
|
||||
*/
|
||||
$boot_dependencies = apply_filters(
|
||||
'jetpack-videopress-dashboard_boot_dependencies',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Dummy script module to ensure dependencies are loaded
|
||||
wp_register_script_module(
|
||||
'jetpack-videopress-dashboard',
|
||||
$build_constants['build_url'] . 'pages/jetpack-videopress-dashboard/loader.js',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Enqueue the boot scripts and styles
|
||||
wp_enqueue_script( 'jetpack-videopress-dashboard-prerequisites' );
|
||||
wp_enqueue_script_module( 'jetpack-videopress-dashboard' );
|
||||
wp_enqueue_style( 'jetpack-videopress-dashboard-prerequisites' );
|
||||
}
|
||||
|
||||
// Output the HTML
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php bloginfo( 'charset' ); ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?php echo esc_html( get_admin_page_title() ); ?></title>
|
||||
<style>
|
||||
html {
|
||||
background: #f1f1f1;
|
||||
color: #444;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
#wpadminbar { display: none; }
|
||||
</style>
|
||||
<?php
|
||||
global $hook_suffix;
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$hook_suffix = 'jetpack-videopress-dashboard';
|
||||
|
||||
// BEGIN see wp-admin/admin-header.php
|
||||
print_admin_styles();
|
||||
print_head_scripts();
|
||||
|
||||
/** This action is documented in wp-admin/admin-header.php */
|
||||
do_action( "admin_head-{$hook_suffix}" ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
|
||||
/** This action is documented in wp-admin/admin-header.php */
|
||||
do_action( 'admin_head' );
|
||||
// END see wp-admin/admin-header.php
|
||||
?>
|
||||
</head>
|
||||
<body class="jetpack-videopress-dashboard">
|
||||
<div id="jetpack-videopress-dashboard-app" style="height: 100vh; box-sizing: border-box;"></div>
|
||||
<?php
|
||||
// BEGIN see wp-admin/admin-footer.php
|
||||
|
||||
/** This action is documented in wp-admin/admin-footer.php */
|
||||
do_action( 'admin_footer', '' );
|
||||
|
||||
// Print import map first so it's available for inline scripts
|
||||
wp_script_modules()->print_import_map();
|
||||
print_footer_scripts();
|
||||
wp_script_modules()->print_enqueued_script_modules();
|
||||
wp_script_modules()->print_script_module_preloads();
|
||||
wp_script_modules()->print_script_module_data();
|
||||
|
||||
/** This action is documented in wp-admin/admin-footer.php */
|
||||
do_action( "admin_footer-{$hook_suffix}" ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
// END see wp-admin/admin-footer.php
|
||||
?>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept admin_init to render the page early.
|
||||
* This bypasses the default WordPress admin template.
|
||||
*/
|
||||
function jetpack_videopress_jetpack_videopress_dashboard_intercept_render() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['page'] ) && 'jetpack-videopress-dashboard' === $_GET['page'] ) {
|
||||
jetpack_videopress_jetpack_videopress_dashboard_render_page();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Hook the interceptor to admin_init
|
||||
add_action( 'admin_init', 'jetpack_videopress_jetpack_videopress_dashboard_intercept_render' );
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Routes registration - Auto-generated by build process.
|
||||
* Registers all routes on their respective page init hooks.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
// Load routes registry
|
||||
$routes_file = __DIR__ . '/routes/registry.php';
|
||||
if ( ! file_exists( $routes_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$routes = require $routes_file;
|
||||
|
||||
// Group routes by page and store in globals for page-specific functions
|
||||
$routes_by_page = array();
|
||||
foreach ( $routes as $route ) {
|
||||
$page_slug = $route['page'];
|
||||
if ( ! isset( $routes_by_page[ $page_slug ] ) ) {
|
||||
$routes_by_page[ $page_slug ] = array();
|
||||
}
|
||||
$routes_by_page[ $page_slug ][] = $route;
|
||||
}
|
||||
|
||||
// Store routes data in globals for each page
|
||||
foreach ( $routes_by_page as $page_slug => $page_routes ) {
|
||||
$page_slug_underscore = str_replace( '-', '_', $page_slug );
|
||||
$global_name = 'jetpack_videopress_' . $page_slug_underscore . '_routes_data';
|
||||
$GLOBALS[ $global_name ] = $page_routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic helper function to register routes for a page.
|
||||
*
|
||||
* @param array $page_routes Array of route data for the page.
|
||||
* @param string $register_function_name Name of the function to call for registering each route.
|
||||
*/
|
||||
function jetpack_videopress_register_page_routes( $page_routes, $register_function_name ) {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/constants.php';
|
||||
|
||||
foreach ( $page_routes as $route ) {
|
||||
$content_handle = null;
|
||||
$route_handle = null;
|
||||
|
||||
// Register content module if exists
|
||||
if ( $route['has_content'] ) {
|
||||
$content_asset_path = __DIR__ . "/routes/{$route['name']}/content.min.asset.php";
|
||||
if ( file_exists( $content_asset_path ) ) {
|
||||
$content_asset = require $content_asset_path;
|
||||
$content_handle = 'jetpack-videopress/routes/' . $route['name'] . '/content';
|
||||
$extension = '.min.js';
|
||||
// Deregister first to override any previously registered version
|
||||
// (e.g., Core's default modules when running as a plugin).
|
||||
wp_deregister_script_module( $content_handle );
|
||||
wp_register_script_module(
|
||||
$content_handle,
|
||||
$build_constants['build_url'] . 'routes/' . $route['name'] . '/content' . $extension,
|
||||
$content_asset['module_dependencies'] ?? array(),
|
||||
$content_asset['version'] ?? false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Register route module if exists
|
||||
if ( $route['has_route'] ) {
|
||||
$route_asset_path = __DIR__ . "/routes/{$route['name']}/route.min.asset.php";
|
||||
if ( file_exists( $route_asset_path ) ) {
|
||||
$route_asset = require $route_asset_path;
|
||||
$route_handle = 'jetpack-videopress/routes/' . $route['name'] . '/route';
|
||||
$extension = '.min.js';
|
||||
// Deregister first to override any previously registered version
|
||||
// (e.g., Core's default modules when running as a plugin).
|
||||
wp_deregister_script_module( $route_handle );
|
||||
wp_register_script_module(
|
||||
$route_handle,
|
||||
$build_constants['build_url'] . 'routes/' . $route['name'] . '/route' . $extension,
|
||||
$route_asset['module_dependencies'] ?? array(),
|
||||
$route_asset['version'] ?? false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Register route with page
|
||||
if ( function_exists( $register_function_name ) ) {
|
||||
call_user_func( $register_function_name, $route['path'], $content_handle, $route_handle );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page-specific route registration functions
|
||||
// Page-specific route registration functions for jetpack-videopress-dashboard
|
||||
/**
|
||||
* Register routes for jetpack-videopress-dashboard page (full-page mode).
|
||||
*/
|
||||
function jetpack_videopress_register_jetpack_videopress_dashboard_page_routes() {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_routes_data;
|
||||
jetpack_videopress_register_page_routes( $jetpack_videopress_jetpack_videopress_dashboard_routes_data, 'jetpack_videopress_register_jetpack_videopress_dashboard_route' );
|
||||
}
|
||||
add_action( 'jetpack-videopress-dashboard_init', 'jetpack_videopress_register_jetpack_videopress_dashboard_page_routes' );
|
||||
|
||||
/**
|
||||
* Register routes for jetpack-videopress-dashboard page (wp-admin mode).
|
||||
*/
|
||||
function jetpack_videopress_register_jetpack_videopress_dashboard_wp_admin_page_routes() {
|
||||
global $jetpack_videopress_jetpack_videopress_dashboard_routes_data;
|
||||
jetpack_videopress_register_page_routes( $jetpack_videopress_jetpack_videopress_dashboard_routes_data, 'jetpack_videopress_register_jetpack_videopress_dashboard_wp_admin_route' );
|
||||
}
|
||||
add_action( 'jetpack-videopress-dashboard-wp-admin_init', 'jetpack_videopress_register_jetpack_videopress_dashboard_wp_admin_page_routes' );
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-keycodes', 'wp-notices', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url', 'wp-warning'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => 'c71428f3744206db4e66');
|
||||
+26
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'bdca920315a6e5637299');
|
||||
+1
@@ -0,0 +1 @@
|
||||
var o={};export{o as route};
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '718cbf8b33cd48bfe1fe');
|
||||
+108
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'bdca920315a6e5637299');
|
||||
+1
@@ -0,0 +1 @@
|
||||
var o={};export{o as route};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Route registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
return array(
|
||||
array(
|
||||
'name' => 'library',
|
||||
'path' => '/library',
|
||||
'page' => 'jetpack-videopress-dashboard',
|
||||
'has_route' => true,
|
||||
'has_content' => true,
|
||||
),
|
||||
array(
|
||||
'name' => 'overview',
|
||||
'path' => '/',
|
||||
'page' => 'jetpack-videopress-dashboard',
|
||||
'has_route' => true,
|
||||
'has_content' => true,
|
||||
),
|
||||
array(
|
||||
'name' => 'settings',
|
||||
'path' => '/settings',
|
||||
'page' => 'jetpack-videopress-dashboard',
|
||||
'has_route' => true,
|
||||
'has_content' => true,
|
||||
),
|
||||
array(
|
||||
'name' => 'video',
|
||||
'path' => '/video/$id',
|
||||
'page' => 'jetpack-videopress-dashboard',
|
||||
'has_route' => true,
|
||||
'has_content' => true,
|
||||
)
|
||||
);
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '91115a6bfcd6cf13775a');
|
||||
+20
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'bdca920315a6e5637299');
|
||||
+1
@@ -0,0 +1 @@
|
||||
var o={};export{o as route};
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '27215135658c663447b6');
|
||||
+32
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'bdca920315a6e5637299');
|
||||
+1
@@ -0,0 +1 @@
|
||||
var o={};export{o as route};
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Script registration - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers a script according to `wp_register_script`. Honors this request by
|
||||
* reassigning internal dependency properties of any script handle already
|
||||
* registered by that name. It does not deregister the original script, to
|
||||
* avoid losing inline scripts which may have been attached.
|
||||
*
|
||||
* @param WP_Scripts $scripts WP_Scripts instance.
|
||||
* @param string $handle Name of the script. Should be unique.
|
||||
* @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
|
||||
* @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
|
||||
* as a query string for cache busting purposes. If version is set to false, a version
|
||||
* number is automatically added equal to current installed WordPress version.
|
||||
* If set to null, no version is added.
|
||||
* @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
|
||||
* Default 'false'.
|
||||
*/
|
||||
function jetpack_videopress_override_script( $scripts, $handle, $src, $deps = array(), $ver = false, $in_footer = false ) {
|
||||
$script = $scripts->query( $handle, 'registered' );
|
||||
if ( $script ) {
|
||||
/*
|
||||
* In many ways, this is a reimplementation of `wp_register_script` but
|
||||
* bypassing consideration of whether a script by the given handle had
|
||||
* already been registered.
|
||||
*/
|
||||
|
||||
// See: `_WP_Dependency::__construct` .
|
||||
$script->src = $src;
|
||||
$script->deps = $deps;
|
||||
$script->ver = $ver;
|
||||
$script->args = $in_footer ? 1 : null;
|
||||
} else {
|
||||
$scripts->add( $handle, $src, $deps, $ver, ( $in_footer ? 1 : null ) );
|
||||
}
|
||||
|
||||
if ( in_array( 'wp-i18n', $deps, true ) ) {
|
||||
$scripts->set_translations( $handle );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all package scripts.
|
||||
*/
|
||||
function jetpack_videopress_register_package_scripts( $scripts ) {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/constants.php';
|
||||
$default_version = ! SCRIPT_DEBUG ? $build_constants['version'] : time();
|
||||
$extension = '.min.js';
|
||||
|
||||
$scripts_dir = __DIR__ . '/scripts';
|
||||
$scripts_file = $scripts_dir . '/registry.php';
|
||||
|
||||
if ( ! file_exists( $scripts_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scripts_data = require $scripts_file;
|
||||
$plugin_dir = dirname( __FILE__ );
|
||||
|
||||
foreach ( $scripts_data as $script_data ) {
|
||||
$asset_file = $scripts_dir . '/' . $script_data['asset'];
|
||||
$asset = file_exists( $asset_file ) ? require $asset_file : array();
|
||||
$dependencies = $asset['dependencies'] ?? array();
|
||||
$version = $asset['version'] ?? $default_version;
|
||||
|
||||
jetpack_videopress_override_script(
|
||||
$scripts,
|
||||
$script_data['handle'],
|
||||
$build_constants['build_url'] . 'scripts/' . $script_data['path'] . $extension,
|
||||
$dependencies,
|
||||
$version,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'wp_default_scripts', 'jetpack_videopress_register_package_scripts' );
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Script registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Style registration - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* Note: These styles can be overridden by calling jetpack_videopress_override_style()
|
||||
* in lib/client-assets.php for complex cases (multiple files, conditional deps, etc.)
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers a style according to `wp_register_style`. Honors this request by
|
||||
* deregistering any style by the same handler before registration.
|
||||
*
|
||||
* @param WP_Styles $styles WP_Styles instance.
|
||||
* @param string $handle Name of the stylesheet. Should be unique.
|
||||
* @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
|
||||
* @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
|
||||
* @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL
|
||||
* as a query string for cache busting purposes. If version is set to false, a version
|
||||
* number is automatically added equal to current installed WordPress version.
|
||||
* If set to null, no version is added.
|
||||
* @param string $media Optional. The media for which this stylesheet has been defined.
|
||||
* Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
|
||||
* '(orientation: portrait)' and '(max-width: 640px)'.
|
||||
*/
|
||||
function jetpack_videopress_override_style( $styles, $handle, $src, $deps = array(), $ver = false, $media = 'all' ) {
|
||||
$style = $styles->query( $handle, 'registered' );
|
||||
if ( $style ) {
|
||||
$styles->remove( $handle );
|
||||
}
|
||||
$styles->add( $handle, $src, $deps, $ver, $media );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all package styles (simple cases only).
|
||||
* Complex cases should be manually registered in lib/client-assets.php.
|
||||
*
|
||||
* @param WP_Styles $styles WP_Styles instance.
|
||||
*/
|
||||
function jetpack_videopress_register_package_styles( $styles ) {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/constants.php';
|
||||
$default_version = ! SCRIPT_DEBUG ? $build_constants['version'] : time();
|
||||
$suffix = '.min';
|
||||
|
||||
$styles_dir = __DIR__ . '/styles';
|
||||
$styles_file = $styles_dir . '/registry.php';
|
||||
|
||||
if ( ! file_exists( $styles_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$styles_data = require $styles_file;
|
||||
$plugin_dir = dirname( __FILE__ );
|
||||
|
||||
foreach ( $styles_data as $style_data ) {
|
||||
jetpack_videopress_override_style(
|
||||
$styles,
|
||||
$style_data['handle'],
|
||||
$build_constants['build_url'] . 'styles/' . $style_data['path'] . $suffix . '.css',
|
||||
$style_data['dependencies'],
|
||||
$default_version
|
||||
);
|
||||
|
||||
// Enable RTL support (WordPress automatically loads -rtl.css variant)
|
||||
$styles->add_data( $style_data['handle'], 'rtl', 'replace' );
|
||||
$styles->add_data( $style_data['handle'], 'suffix', $suffix );
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'wp_default_styles', 'jetpack_videopress_register_package_styles' );
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Style registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_videopress
|
||||
*/
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
+522
@@ -0,0 +1,522 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Access Control.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Abstract_Token_Subscription_Service;
|
||||
use Automattic\Jetpack\Modules;
|
||||
use VIDEOPRESS_PRIVACY;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* VideoPress video access control utilities.
|
||||
*
|
||||
* Note: this is also being used on WordPress.com.
|
||||
* Use IS_WPCOM checks for functionality that is specific to WPCOM/Jetpack.
|
||||
*/
|
||||
class Access_Control {
|
||||
|
||||
/**
|
||||
* Singleton Access_Control instance.
|
||||
*
|
||||
* @var Access_Control
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Guid to subscription plan, store, for when used inline on a page.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $guids_to_subscriptions = array();
|
||||
|
||||
/**
|
||||
* Set that this guid is controlled by a subscription.
|
||||
*
|
||||
* @param string $guid The guid to set.
|
||||
* @param string|int $subscription_id The subscription to set.
|
||||
*
|
||||
* @return Access_Control
|
||||
*/
|
||||
public function set_guid_subscription( $guid, $subscription_id ) {
|
||||
$this->guids_to_subscriptions[ $guid ] = $subscription_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription for a guid.
|
||||
*
|
||||
* @param string $guid The guid to get.
|
||||
*
|
||||
* @return string|int|false
|
||||
*/
|
||||
public function get_subscription_plan_id( $guid ) {
|
||||
return $this->guids_to_subscriptions[ $guid ] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if Jetpack Memberships are available.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function jetpack_memberships_available() {
|
||||
return class_exists( '\Jetpack_Memberships' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if Jetpack Subscriptions are available.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function jetpack_subscriptions_available() {
|
||||
$is_module_active = ( new Modules() )->is_active( 'subscriptions' );
|
||||
if ( ! $is_module_active ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( function_exists( '\Automattic\Jetpack\Extensions\Premium_Content\subscription_service' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! defined( 'JETPACK__PLUGIN_DIR' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscription_service_file_path = JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php';
|
||||
if ( ! file_exists( $subscription_service_file_path ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
require_once $subscription_service_file_path;
|
||||
|
||||
return function_exists( '\Automattic\Jetpack\Extensions\Premium_Content\subscription_service' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check default user access. By default, subscribers or higher can view videos.
|
||||
*
|
||||
* @param WP_Post $post_to_check The post to check.
|
||||
*
|
||||
* @return bool
|
||||
**/
|
||||
private function get_default_user_capability_for_post( $post_to_check ) {
|
||||
if ( ! isset( $post_to_check->ID ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$default_auth = current_user_can( 'read_post', $post_to_check->ID );
|
||||
|
||||
return $default_auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user can access restricted content and builds the restriction_details array.
|
||||
*
|
||||
* @param string $guid the video guid.
|
||||
* @param int $embedded_post_id the post id.
|
||||
* @param int $selected_plan_id the selected plan id if applicable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function build_restriction_details( $guid, $embedded_post_id, $selected_plan_id ) {
|
||||
$post_to_check = get_post( $embedded_post_id );
|
||||
|
||||
if ( empty( $post_to_check ) ) {
|
||||
$restriction_details = $this->default_video_restriction_details( false );
|
||||
return $this->filter_video_restriction_details( $restriction_details, $guid, $embedded_post_id, $selected_plan_id );
|
||||
}
|
||||
|
||||
$default_auth = $this->get_default_user_capability_for_post( $post_to_check );
|
||||
$restriction_details = $this->default_video_restriction_details( $default_auth );
|
||||
|
||||
if ( $this->jetpack_memberships_available() ) {
|
||||
$post_access_level = \Jetpack_Memberships::get_post_access_level( $embedded_post_id );
|
||||
if ( 'everybody' !== $post_access_level ) {
|
||||
$memberships_can_view_post = \Jetpack_Memberships::user_can_view_post( $embedded_post_id );
|
||||
$restriction_details = $this->get_subscriber_only_restriction_details( $default_auth );
|
||||
$restriction_details['can_access'] = $memberships_can_view_post;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->check_block_level_access(
|
||||
$restriction_details,
|
||||
$guid,
|
||||
$embedded_post_id,
|
||||
$selected_plan_id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user can access restricted block content and updates the restriction_details array.
|
||||
*
|
||||
* @param array $restriction_details the restriction details array.
|
||||
* @param string $guid the video guid.
|
||||
* @param int $embedded_post_id the post id.
|
||||
* @param int $selected_plan_id the selected plan id if applicable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function check_block_level_access( $restriction_details, $guid, $embedded_post_id, $selected_plan_id ) {
|
||||
if ( $this->jetpack_subscriptions_available() && $selected_plan_id > 0 ) {
|
||||
$restriction_details = $this->get_subscriber_only_restriction_details( $restriction_details['can_access'] );
|
||||
$paywall = \Automattic\Jetpack\Extensions\Premium_Content\subscription_service();
|
||||
|
||||
// Only paid subscribers should be granted access to the premium content.
|
||||
$access_level = '';
|
||||
if ( class_exists( Abstract_Token_Subscription_Service::class ) ) {
|
||||
$access_level = Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS;
|
||||
}
|
||||
|
||||
$can_view = $paywall->visitor_can_view_content( array( $selected_plan_id ), $access_level );
|
||||
$restriction_details['can_access'] = $can_view || current_user_can( 'edit_post', $embedded_post_id ); // Editors can always view the content.
|
||||
}
|
||||
|
||||
return $this->filter_video_restriction_details(
|
||||
$restriction_details,
|
||||
$guid,
|
||||
$embedded_post_id,
|
||||
$selected_plan_id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default restriction_details for a video.
|
||||
*
|
||||
* @param bool $default_can_access The default auth.
|
||||
*
|
||||
* @return array
|
||||
**/
|
||||
private function get_subscriber_only_restriction_details( $default_can_access = false ) {
|
||||
return array(
|
||||
'provider' => 'jetpack_memberships',
|
||||
'title' => __( 'This video is subscriber-only', 'jetpack-videopress-pkg' ),
|
||||
'unauthorized_message' => __( 'You need to be subscribed to view this video', 'jetpack-videopress-pkg' ),
|
||||
'can_access' => $default_can_access,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters restriction details.
|
||||
*
|
||||
* @param array $video_restriction_details The restriction details.
|
||||
* @param string $guid The video guid.
|
||||
* @param int $embedded_post_id The post id.
|
||||
* @param int $selected_plan_id The selected plan id if applicable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function filter_video_restriction_details( $video_restriction_details, $guid, $embedded_post_id, $selected_plan_id ) {
|
||||
/**
|
||||
* Filters the video restriction details.
|
||||
*
|
||||
* @param array $video_restriction_details The restriction details.
|
||||
* @param string $guid The video guid.
|
||||
* @param int $embedded_post_id The post id.
|
||||
* @param int $selected_plan_id The selected plan id if applicable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
return (array) apply_filters( 'videopress_video_restriction_details', $video_restriction_details, $guid, $embedded_post_id, $selected_plan_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default restriction_details for a video.
|
||||
*
|
||||
* @param bool $default_can_access The default auth.
|
||||
*
|
||||
* @return array
|
||||
**/
|
||||
private function default_video_restriction_details( $default_can_access = false ) {
|
||||
$restriction_details = array(
|
||||
'version' => '1',
|
||||
'provider' => 'auth',
|
||||
'title' => __( 'Unauthorized', 'jetpack-videopress-pkg' ),
|
||||
'unauthorized_message' => __( 'Unauthorized', 'jetpack-videopress-pkg' ),
|
||||
'can_access' => $default_can_access,
|
||||
);
|
||||
|
||||
return $restriction_details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a given post actually embeds a given VideoPress GUID.
|
||||
*
|
||||
* Used to prevent the embedded post id — which arrives from request input — from being
|
||||
* treated as an authorization context when it has no relationship to the requested video.
|
||||
* Matching the attachment id itself is not treated as proof of embedding: attachment ids
|
||||
* are enumerable via the media REST route and would otherwise provide a second path around
|
||||
* this check whenever the attachment has no parent and falls back to the `read` capability.
|
||||
*
|
||||
* @param int $embedded_post_id The post id claimed as the embedding context.
|
||||
* @param string $guid The video guid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function post_embeds_videopress_guid( $embedded_post_id, $guid ) {
|
||||
$post = get_post( $embedded_post_id );
|
||||
if ( ! $post instanceof WP_Post || empty( $post->post_content ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the guid is nowhere in the content, neither the block nor the shortcode scan can match.
|
||||
if ( false === strpos( $post->post_content, $guid ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->post_content_has_videopress_block( $post->post_content, $guid ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $this->post_content_has_videopress_shortcode( $post->post_content, $guid ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->post_content_has_videopress_url( $post->post_content, $guid );
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk parsed blocks (including inner blocks) looking for a videopress/video block
|
||||
* whose guid attribute matches.
|
||||
*
|
||||
* @param string $post_content The post content to scan.
|
||||
* @param string $guid The video guid to match.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function post_content_has_videopress_block( $post_content, $guid ) {
|
||||
if ( false === strpos( $post_content, 'wp:videopress/video' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->blocks_contain_videopress_guid( parse_blocks( $post_content ), $guid );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scans a parsed block tree for a videopress/video block whose guid attribute matches.
|
||||
*
|
||||
* @param array $blocks Parsed blocks (as returned by parse_blocks() or an innerBlocks array).
|
||||
* @param string $guid The video guid to match.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function blocks_contain_videopress_guid( $blocks, $guid ) {
|
||||
foreach ( $blocks as $block ) {
|
||||
if (
|
||||
isset( $block['blockName'] ) && 'videopress/video' === $block['blockName']
|
||||
&& isset( $block['attrs']['guid'] ) && $block['attrs']['guid'] === $guid
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] )
|
||||
&& $this->blocks_contain_videopress_guid( $block['innerBlocks'], $guid )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a [videopress GUID] or [wpvideo GUID] shortcode whose first positional
|
||||
* argument matches the given guid.
|
||||
*
|
||||
* @param string $post_content The post content to scan.
|
||||
* @param string $guid The video guid to match.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function post_content_has_videopress_shortcode( $post_content, $guid ) {
|
||||
if ( false === stripos( $post_content, '[videopress' ) && false === stripos( $post_content, '[wpvideo' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pattern = get_shortcode_regex( array( 'videopress', 'wpvideo' ) );
|
||||
$count = preg_match_all( '/' . $pattern . '/', $post_content, $matches, PREG_SET_ORDER );
|
||||
if ( false === $count || 0 === $count ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $matches as $match ) {
|
||||
$atts = shortcode_parse_atts( $match[3] );
|
||||
if ( ! is_array( $atts ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only the positional argument identifies the video; named attributes must not satisfy the binding check.
|
||||
if ( isset( $atts[0] ) && is_string( $atts[0] ) && $atts[0] === $guid ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a canonical VideoPress URL referencing the given guid. Covers oEmbed
|
||||
* inserts, core/embed blocks, core/video blocks, and core [video] shortcodes
|
||||
* whose src/mp4 attributes resolve to a VideoPress URL.
|
||||
*
|
||||
* @param string $post_content The post content to scan.
|
||||
* @param string $guid The video guid to match.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function post_content_has_videopress_url( $post_content, $guid ) {
|
||||
if ( ! preg_match_all( '#https?://[^\s"\'<>)]+#i', $post_content, $matches ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $matches[0] as $url ) {
|
||||
if ( $guid === Utils::extract_videopress_guid_from_url( $url ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user can view the provided video. Only ever gets fired if site-wide private videos are enabled.
|
||||
*
|
||||
* Filterable for 3rd party plugins.
|
||||
*
|
||||
* @param string $guid The video id being checked.
|
||||
* @param int $embedded_post_id The post id the video is embedded in or 0.
|
||||
* @param int $selected_plan_id The plan id the earn block this video is embedded in has.
|
||||
*/
|
||||
public function is_current_user_authed_for_video( $guid, $embedded_post_id, $selected_plan_id = 0 ) {
|
||||
if ( current_user_can( 'upload_files' ) ) {
|
||||
return $this->filter_is_current_user_authed_for_video( true, $guid, $embedded_post_id );
|
||||
}
|
||||
|
||||
$attachment = false;
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
$video_info = video_get_info_by_guid( $guid );
|
||||
if ( ! empty( $video_info ) ) {
|
||||
$attachment = get_blog_post( $video_info->blog_id, $video_info->post_id );
|
||||
}
|
||||
} else {
|
||||
$attachment = videopress_get_post_by_guid( $guid );
|
||||
}
|
||||
|
||||
if ( ! $attachment ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$video_info = video_get_info_by_blogpostid( get_current_blog_id(), $attachment->ID );
|
||||
if ( null === $video_info->guid ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Default missing privacy_setting to SITE_DEFAULT to avoid an
|
||||
* undefined-property warning and make the site-level fallback explicit.
|
||||
*/
|
||||
$privacy_setting = $video_info->privacy_setting ?? VIDEOPRESS_PRIVACY::SITE_DEFAULT;
|
||||
|
||||
$embedded_post_id = (int) $embedded_post_id;
|
||||
if (
|
||||
$embedded_post_id
|
||||
&& VIDEOPRESS_PRIVACY::IS_PUBLIC !== $privacy_setting
|
||||
&& ! $this->post_embeds_videopress_guid( $embedded_post_id, $guid )
|
||||
) {
|
||||
$embedded_post_id = 0;
|
||||
}
|
||||
|
||||
$is_user_authed = false;
|
||||
|
||||
// Determine if video is public, private or use site default.
|
||||
switch ( $privacy_setting ) {
|
||||
case VIDEOPRESS_PRIVACY::IS_PUBLIC:
|
||||
$is_user_authed = true;
|
||||
break;
|
||||
case VIDEOPRESS_PRIVACY::IS_PRIVATE:
|
||||
$restriction_details = $this->build_restriction_details( $guid, $embedded_post_id, $selected_plan_id );
|
||||
$is_user_authed = $restriction_details['can_access'];
|
||||
break;
|
||||
case VIDEOPRESS_PRIVACY::SITE_DEFAULT:
|
||||
default:
|
||||
$is_videopress_private_for_site = Data::get_videopress_videos_private_for_site();
|
||||
$is_user_authed = true;
|
||||
if ( $is_videopress_private_for_site ) {
|
||||
$restriction_details = $this->build_restriction_details( $guid, $embedded_post_id, $selected_plan_id );
|
||||
$is_user_authed = $restriction_details['can_access'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides video view authorization for current user.
|
||||
*
|
||||
* Example of making all videos public:
|
||||
*
|
||||
* function jp_example_override_video_auth( $is_user_authed, $guid ) {
|
||||
* return true
|
||||
* };
|
||||
* add_filter( 'videopress_is_current_user_authed_for_video', 'jp_example_override_video_auth', 10, 2 );
|
||||
*
|
||||
* @param bool $is_user_authed The current user authorization state.
|
||||
* @param string $guid The video's unique identifier.
|
||||
* @param int|null $embedded_post_id The post the video is embedded..
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
return $this->filter_is_current_user_authed_for_video( $is_user_authed, $guid, $embedded_post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides video view authorization for current user.
|
||||
*
|
||||
* @param bool $is_user_authed The current user authorization state.
|
||||
* @param string $guid The video's unique identifier.
|
||||
* @param int|null $embedded_post_id The post the video is embedded..
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function filter_is_current_user_authed_for_video( $is_user_authed, $guid, $embedded_post_id ) {
|
||||
/**
|
||||
* Overrides video view authorization for current user.
|
||||
*
|
||||
* Example of making all videos public:
|
||||
*
|
||||
* function jp_example_override_video_auth( $is_user_authed, $guid ) {
|
||||
* return true
|
||||
* };
|
||||
* add_filter( 'videopress_is_current_user_authed_for_video', 'jp_example_override_video_auth', 10, 2 );
|
||||
*
|
||||
* @param bool $is_user_authed The current user authorization state.
|
||||
* @param string $guid The video's unique identifier.
|
||||
* @param int|null $embedded_post_id The post the video is embedded..
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
return (bool) apply_filters( 'videopress_is_current_user_authed_for_video', $is_user_authed, $guid, $embedded_post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the proper blog id depending on Jetpack or WP.com
|
||||
*
|
||||
* @return int the blog id
|
||||
*/
|
||||
public function get_videopress_blog_id() {
|
||||
return \Jetpack_Options::get_option( 'id' );
|
||||
}
|
||||
}
|
||||
+544
File diff suppressed because one or more lines are too long
+293
@@ -0,0 +1,293 @@
|
||||
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
|
||||
/**
|
||||
* VideoPress AJAX action handlers and utilities.
|
||||
*
|
||||
* Note: this is also being used on WordPress.com.
|
||||
* Use IS_WPCOM checks for functionality that is specific to WPCOM/Jetpack.
|
||||
*/
|
||||
class AJAX {
|
||||
|
||||
/**
|
||||
* Singleton AJAX instance.
|
||||
*
|
||||
* @var AJAX
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Private AJAX constructor.
|
||||
*
|
||||
* Use the AJAX::init() method to get an instance.
|
||||
*/
|
||||
private function __construct() {
|
||||
add_action( 'wp_ajax_videopress-get-upload-token', array( $this, 'wp_ajax_videopress_get_upload_token' ) );
|
||||
add_action( 'wp_ajax_videopress-get-upload-jwt', array( $this, 'wp_ajax_videopress_get_upload_jwt' ) );
|
||||
add_action( 'wp_ajax_nopriv_videopress-get-playback-jwt', array( $this, 'wp_ajax_videopress_get_playback_jwt' ) );
|
||||
add_action( 'wp_ajax_videopress-get-playback-jwt', array( $this, 'wp_ajax_videopress_get_playback_jwt' ) );
|
||||
|
||||
add_action(
|
||||
'wp_ajax_videopress-update-transcoding-status',
|
||||
array(
|
||||
$this,
|
||||
'wp_ajax_update_transcoding_status',
|
||||
),
|
||||
-1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the AJAX and get back a singleton instance.
|
||||
*
|
||||
* @return AJAX
|
||||
*/
|
||||
public static function init() {
|
||||
if ( self::$instance === null ) {
|
||||
self::$instance = new AJAX();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a guid.
|
||||
*
|
||||
* @param string $guid The guid to validate.
|
||||
*
|
||||
* @return bool
|
||||
**/
|
||||
private function is_valid_guid( $guid ) {
|
||||
if ( empty( $guid ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
preg_match( '/^[a-z0-9]{8}$/i', $guid, $matches );
|
||||
|
||||
if ( empty( $matches ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax method that is used by the VideoPress player to get a token to play a video.
|
||||
*
|
||||
* This is used for both logged in and logged out users.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wp_ajax_videopress_get_playback_jwt() {
|
||||
$guid = filter_input( INPUT_POST, 'guid' );
|
||||
$embedded_post_id = filter_input( INPUT_POST, 'post_id', FILTER_VALIDATE_INT );
|
||||
$selected_plan_id = filter_input( INPUT_POST, 'subscription_plan_id' );
|
||||
|
||||
if ( empty( $embedded_post_id ) ) {
|
||||
$embedded_post_id = 0;
|
||||
}
|
||||
|
||||
if ( empty( $guid ) || ! $this->is_valid_guid( $guid ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'need a guid', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->is_current_user_authed_for_video( $guid, $embedded_post_id, $selected_plan_id ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'You cannot view this video.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->request_jwt_from_wpcom( $guid );
|
||||
|
||||
if ( empty( $token ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress playback JWT. Please try again later. (empty upload token)', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_wp_error( $token ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload JWT. Please try again later.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_success( array( 'jwt' => $token ), null, JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user can view the provided video. Only ever gets fired if site-wide private videos are enabled.
|
||||
*
|
||||
* Filterable for 3rd party plugins.
|
||||
*
|
||||
* @param string $guid The video id being checked.
|
||||
* @param int $embedded_post_id The post id the video is embedded in or 0.
|
||||
* @param int $selected_plan_id The plan id the earn block this video is embedded in has.
|
||||
*/
|
||||
private function is_current_user_authed_for_video( $guid, $embedded_post_id, $selected_plan_id = 0 ) {
|
||||
return Access_Control::instance()->is_current_user_authed_for_video( $guid, $embedded_post_id, $selected_plan_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests JWT from wpcom.
|
||||
*
|
||||
* @param string $guid The video id being checked.
|
||||
*/
|
||||
private function request_jwt_from_wpcom( $guid ) {
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM && function_exists( 'video_wpcom_get_playback_jwt_for_guid' ) ) {
|
||||
$jwt_data = video_wpcom_get_playback_jwt_for_guid( $guid );
|
||||
if ( is_wp_error( $jwt_data ) ) {
|
||||
return false;
|
||||
}
|
||||
return $jwt_data->metadata_token;
|
||||
}
|
||||
|
||||
$video_blog_id = $this->get_videopress_blog_id();
|
||||
$args = array(
|
||||
'method' => 'POST',
|
||||
);
|
||||
|
||||
$endpoint = "sites/{$video_blog_id}/media/videopress-playback-jwt/{$guid}";
|
||||
$result = Client::wpcom_json_api_request_as_blog( $endpoint, 'v2', $args, null, 'wpcom' );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response = json_decode( $result['body'], true );
|
||||
|
||||
if ( empty( $response['metadata_token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response['metadata_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax method that is used by the VideoPress uploader to get a token to upload a file to the wpcom api.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wp_ajax_videopress_get_upload_jwt() {
|
||||
if ( ! current_user_can( 'upload_files' ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'You do not have permission to upload files.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$video_blog_id = $this->get_videopress_blog_id();
|
||||
$args = array(
|
||||
'method' => 'POST',
|
||||
);
|
||||
|
||||
$endpoint = "sites/{$video_blog_id}/media/videopress-upload-jwt";
|
||||
$result = Client::wpcom_json_api_request_as_blog( $endpoint, 'v2', $args, null, 'wpcom' );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload JWT. Please try again later.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$response = json_decode( $result['body'], true );
|
||||
|
||||
if ( empty( $response['upload_token'] ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload JWT. Please try again later. (empty upload token)', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$response['upload_action_url'] = videopress_make_resumable_upload_path( $video_blog_id );
|
||||
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_success( $response, null, JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax method that is used by the VideoPress uploader to get a token to upload a file to the wpcom api.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wp_ajax_videopress_get_upload_token() {
|
||||
if ( ! current_user_can( 'upload_files' ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'You do not have permission to upload files.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$video_blog_id = $this->get_videopress_blog_id();
|
||||
|
||||
$args = array(
|
||||
'method' => 'POST',
|
||||
);
|
||||
|
||||
$endpoint = "sites/{$video_blog_id}/media/token";
|
||||
$result = Client::wpcom_json_api_request_as_blog( $endpoint, Client::WPCOM_JSON_API_VERSION, $args );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload token. Please try again later.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$response = json_decode( $result['body'], true );
|
||||
|
||||
if ( empty( $response['upload_token'] ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload token. Please try again later.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$response['upload_action_url'] = videopress_make_media_upload_path( $video_blog_id );
|
||||
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_success( $response, null, JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax action to update the video transcoding status from the WPCOM API.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wp_ajax_update_transcoding_status() {
|
||||
if ( ! isset( $_POST['post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Informational AJAX response.
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'A valid post_id is required.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = (int) $_POST['post_id']; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
|
||||
if ( ! videopress_update_meta_data( $post_id ) ) {
|
||||
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
wp_send_json_error( array( 'message' => __( 'That post does not have a VideoPress video associated to it.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
|
||||
return;
|
||||
}
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'Status updated', 'jetpack-videopress-pkg' ),
|
||||
'status' => videopress_get_transcoding_status( $post_id ),
|
||||
),
|
||||
null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
|
||||
JSON_UNESCAPED_SLASHES
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the proper blog id depending on Jetpack or WP.com
|
||||
*
|
||||
* @return int the blog id
|
||||
*/
|
||||
public function get_videopress_blog_id() {
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
return get_current_blog_id();
|
||||
}
|
||||
|
||||
$options = Options::get_options();
|
||||
return $options['shadow_blog_id'];
|
||||
}
|
||||
}
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Attachment_Handler
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* VideoPress Attachment_Handler class.
|
||||
*/
|
||||
class Attachment_Handler {
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*
|
||||
* This method should be called only once by the Initializer class. Do not call this method again.
|
||||
*/
|
||||
public static function init() {
|
||||
|
||||
if ( ! Status::is_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'wp_get_attachment_url', array( __CLASS__, 'maybe_get_attached_url_for_videopress' ), 10, 2 );
|
||||
add_filter( 'get_attached_file', array( __CLASS__, 'maybe_get_attached_url_for_videopress' ), 10, 2 );
|
||||
|
||||
if ( Current_Plan::supports( 'videopress' ) ) {
|
||||
add_filter( 'upload_mimes', array( __CLASS__, 'add_video_upload_mimes' ), 999 );
|
||||
}
|
||||
|
||||
add_filter( 'pre_delete_attachment', array( __CLASS__, 'delete_video_wpcom' ), 10, 2 );
|
||||
add_filter( 'wp_mime_type_icon', array( __CLASS__, 'wp_mime_type_icon' ), 10, 3 );
|
||||
add_filter( 'wp_video_extensions', array( __CLASS__, 'add_videopress_extenstion' ) );
|
||||
|
||||
add_filter( 'wp_prepare_attachment_for_js', array( __CLASS__, 'prepare_attachment_for_js' ) );
|
||||
add_filter( 'ajax_query_attachments_args', array( __CLASS__, 'ajax_query_attachments_args' ) );
|
||||
add_action( 'pre_get_posts', array( __CLASS__, 'media_list_table_query' ) );
|
||||
|
||||
add_filter( 'user_has_cap', array( __CLASS__, 'disable_delete_if_disconnected' ), 10, 3 );
|
||||
|
||||
add_action( 'admin_print_scripts-upload.php', array( __CLASS__, 'enqueue_media_library_poll' ) );
|
||||
add_action( 'admin_print_styles-upload.php', array( __CLASS__, 'enqueue_media_library_styles' ) );
|
||||
add_filter( 'heartbeat_received', array( __CLASS__, 'heartbeat_received' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the VideoPress URL for the give post id, otherwise returns the provided default.
|
||||
*
|
||||
* This is an attachment-based filter handler.
|
||||
*
|
||||
* @param string $default The default return value if post id is not a VideoPress video.
|
||||
* @param int $post_id The post id for the current attachment.
|
||||
*/
|
||||
public static function maybe_get_attached_url_for_videopress( $default, $post_id ) {
|
||||
$videopress_url = videopress_get_attachment_url( $post_id );
|
||||
|
||||
if ( null !== $videopress_url ) {
|
||||
return $videopress_url;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that all video mimes are added in, as multi site installs can remove them.
|
||||
*
|
||||
* @param array $existing_mimes Mime types to extend/filter.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_video_upload_mimes( $existing_mimes = array() ) {
|
||||
$mime_types = wp_get_mime_types();
|
||||
$video_types = array_filter( $mime_types, array( __CLASS__, 'filter_video_mimes' ) );
|
||||
|
||||
foreach ( $video_types as $key => $value ) {
|
||||
$existing_mimes[ $key ] = $value;
|
||||
}
|
||||
|
||||
// Make sure that videopress mimes are considered videos.
|
||||
$existing_mimes['videopress'] = 'video/videopress';
|
||||
|
||||
return $existing_mimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter designed to get rid of non video mime types.
|
||||
*
|
||||
* @param string $value Mime type to filter.
|
||||
* @return int
|
||||
*/
|
||||
public static function filter_video_mimes( $value ) {
|
||||
return preg_match( '@^video/@', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete a VideoPress video from wp.com.
|
||||
* Will block the deletion from continuing if certain errors return from the wp.com API.
|
||||
*
|
||||
* @param Boolean $delete if the deletion should occur or not (unused).
|
||||
* @param WP_Post $post the post object.
|
||||
*
|
||||
* @return null|WP_Error|Boolean null if deletion should continue.
|
||||
*/
|
||||
public static function delete_video_wpcom( $delete, $post ) {
|
||||
if ( ! is_videopress_attachment( $post->ID ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$guid = get_post_meta( $post->ID, 'videopress_guid', true );
|
||||
if ( empty( $guid ) ) {
|
||||
self::delete_video_poster_attachment( $post->ID );
|
||||
return null;
|
||||
}
|
||||
|
||||
// Phone home and have wp.com delete the VideoPress entry and files.
|
||||
$wpcom_response = Client::wpcom_json_api_request_as_blog(
|
||||
sprintf( '/videos/%s/delete', $guid ),
|
||||
'1.1',
|
||||
array( 'method' => 'POST' ),
|
||||
array( 'user_id' => get_current_user_id() )
|
||||
);
|
||||
|
||||
if ( is_wp_error( $wpcom_response ) ) {
|
||||
return $wpcom_response;
|
||||
}
|
||||
|
||||
// Upon success or a 404 (video already deleted on wp.com), return null to allow the deletion to continue.
|
||||
if ( 200 === $wpcom_response['response']['code'] || 404 === $wpcom_response['response']['code'] ) {
|
||||
self::delete_video_poster_attachment( $post->ID );
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise we stop the deletion from proceeding.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a video poster attachment if it exists.
|
||||
*
|
||||
* @param int $attachment_id the WP attachment id.
|
||||
*/
|
||||
private static function delete_video_poster_attachment( $attachment_id ) {
|
||||
$thumbnail_id = get_post_meta( $attachment_id, '_thumbnail_id', true );
|
||||
if ( ! empty( $thumbnail_id ) ) {
|
||||
// Let's ensure this is a VP poster image before we delete it.
|
||||
if ( '1' === get_post_meta( $thumbnail_id, 'videopress_poster_image', true ) ) {
|
||||
// This call triggers the `delete_video_wpcom` filter again but it bails early at the is_videopress_attachment() check.
|
||||
wp_delete_attachment( $thumbnail_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the mime type icon.
|
||||
*
|
||||
* @param string $icon Icon path.
|
||||
* @param string $mime Mime type.
|
||||
* @param int $post_id Post ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function wp_mime_type_icon( $icon, $mime, $post_id ) {
|
||||
|
||||
if ( $mime !== 'video/videopress' ) {
|
||||
return $icon;
|
||||
}
|
||||
|
||||
$status = get_post_meta( $post_id, 'videopress_status', true );
|
||||
|
||||
if ( $status === 'complete' ) {
|
||||
return $icon;
|
||||
}
|
||||
|
||||
return 'https://wordpress.com/wp-content/mu-plugins/videopress/images/media-video-processing-icon.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the list of supported video formats.
|
||||
*
|
||||
* @param array $extensions Supported video formats.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function add_videopress_extenstion( $extensions ) {
|
||||
$extensions[] = 'videopress';
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that any Video that has a VideoPress GUID passes that data back.
|
||||
*
|
||||
* @param array $post Attachment data array.
|
||||
* @return array
|
||||
*/
|
||||
public static function prepare_attachment_for_js( $post ) {
|
||||
if ( 'video' === $post['type'] ) {
|
||||
$guid = get_post_meta( $post['id'], 'videopress_guid', true );
|
||||
if ( $guid ) {
|
||||
$post['videopress_guid'] = $guid;
|
||||
}
|
||||
$status = get_post_meta( $post['id'], 'videopress_status', true );
|
||||
if ( $status ) {
|
||||
$post['videopress_status'] = $status;
|
||||
}
|
||||
}
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Media Grid:
|
||||
* Filter out any videopress video posters that we've downloaded,
|
||||
* so that they don't seem to display twice.
|
||||
*
|
||||
* @param array $args Query variables.
|
||||
*/
|
||||
public static function ajax_query_attachments_args( $args ) {
|
||||
$meta_query = array(
|
||||
array(
|
||||
'key' => 'videopress_poster_image',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
);
|
||||
|
||||
// If there was already a meta query, let's AND it via
|
||||
// nesting it with our new one. No need to specify the
|
||||
// relation, as it defaults to AND.
|
||||
if ( ! empty( $args['meta_query'] ) ) {
|
||||
$meta_query[] = $args['meta_query'];
|
||||
}
|
||||
$args['meta_query'] = $meta_query;
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Media List:
|
||||
* Do the same as `videopress_ajax_query_attachments_args()` but for the list view.
|
||||
*
|
||||
* @param array $query WP_Query instance.
|
||||
*/
|
||||
public static function media_list_table_query( $query ) {
|
||||
|
||||
if (
|
||||
! function_exists( 'get_current_screen' )
|
||||
|| get_current_screen() === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_admin() && $query->is_main_query() && ( 'upload' === get_current_screen()->id ) ) {
|
||||
$meta_query = array(
|
||||
array(
|
||||
'key' => 'videopress_poster_image',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
);
|
||||
|
||||
$old_meta_query = $query->get( 'meta_query' );
|
||||
if ( $old_meta_query ) {
|
||||
$meta_query[] = $old_meta_query;
|
||||
}
|
||||
|
||||
$query->set( 'meta_query', $meta_query );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to disable the `delete_post` capability
|
||||
* for VideoPress attachments if the current user is
|
||||
* not connected.
|
||||
*
|
||||
* @param array $allcaps All the capabilities of the user.
|
||||
* @param array $cap [0] Required capability.
|
||||
* @param array $args [0] Requested capability.
|
||||
* [1] User ID.
|
||||
* [2] Associated object ID.
|
||||
* @return array the filtered array of capabilities.
|
||||
*/
|
||||
public static function disable_delete_if_disconnected( $allcaps, $cap, $args ) {
|
||||
|
||||
// Only apply this filter to `delete_post` checks
|
||||
if ( ! isset( $args[0] ) || 'delete_post' !== $args[0] ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
// Only apply this filter to VideoPress attachments
|
||||
if ( ! isset( $args[2] ) || ! is_videopress_attachment( $args[2] ) ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
// Set the capability to false if the user can't perform the actions
|
||||
if ( ! Data::can_perform_action() ) {
|
||||
$allcaps[ $cap[0] ] = false;
|
||||
}
|
||||
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the script that hooks into WP Heartbeat to refresh
|
||||
* processing VideoPress video data in the media library grid.
|
||||
*
|
||||
* @since 0.35.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue_media_library_poll() {
|
||||
wp_enqueue_script(
|
||||
'videopress-media-library-poll',
|
||||
plugins_url( 'js/media-library-poll.js', __FILE__ ),
|
||||
array( 'jquery', 'heartbeat', 'media-grid' ),
|
||||
Package_Version::PACKAGE_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
add_filter( 'heartbeat_settings', array( __CLASS__, 'heartbeat_settings' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inline styles for the media library grid on the upload page.
|
||||
*
|
||||
* @since 0.35.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue_media_library_styles() {
|
||||
// Constrain poster images so they fit within the media library grid cell.
|
||||
wp_add_inline_style(
|
||||
'media-views',
|
||||
'.attachment-preview.type-video .thumbnail .centered img.thumbnail { max-width: 100%; max-height: 100%; }'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowers the Heartbeat minimum interval on the media library page
|
||||
* so the polling script can request a faster tick rate.
|
||||
*
|
||||
* @since 0.35.4
|
||||
*
|
||||
* @param array $settings Heartbeat settings.
|
||||
* @return array
|
||||
*/
|
||||
public static function heartbeat_settings( $settings ) {
|
||||
$settings['minimalInterval'] = 5;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Heartbeat API handler that checks the processing status of VideoPress videos.
|
||||
*
|
||||
* @since 0.35.4
|
||||
*
|
||||
* @param array $response The Heartbeat response.
|
||||
* @param array $data The data sent with the Heartbeat request.
|
||||
* @return array
|
||||
*/
|
||||
public static function heartbeat_received( $response, $data ) {
|
||||
if ( empty( $data['videopress_processing_ids'] ) || ! is_array( $data['videopress_processing_ids'] ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$statuses = array();
|
||||
foreach ( $data['videopress_processing_ids'] as $id ) {
|
||||
$id = (int) $id;
|
||||
|
||||
if ( ! current_user_can( 'edit_post', $id ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$status = get_post_meta( $id, 'videopress_status', true );
|
||||
if ( $status ) {
|
||||
$statuses[ $id ] = $status;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $statuses ) ) {
|
||||
$response['videopress_processing_status'] = $statuses;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Block Editor Content
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* VideoPress block editor class for content generation
|
||||
*/
|
||||
class Block_Editor_Content {
|
||||
/**
|
||||
* Initializer
|
||||
*
|
||||
* This method should be called only once by the Initializer class. Do not call this method again.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( ! Status::is_standalone_plugin_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the videopress shortcodes added by the Jetpack plugin.
|
||||
if ( shortcode_exists( 'videopress' ) ) {
|
||||
remove_shortcode( 'videopress' );
|
||||
}
|
||||
if ( shortcode_exists( 'wpvideo' ) ) {
|
||||
remove_shortcode( 'wpvideo' );
|
||||
}
|
||||
|
||||
add_shortcode( 'videopress', array( static::class, 'videopress_embed_shortcode' ) );
|
||||
add_shortcode( 'wpvideo', array( static::class, 'videopress_embed_shortcode' ) );
|
||||
|
||||
add_filter( 'wp_video_shortcode_override', array( static::class, 'video_shortcode_override' ), 10, 4 );
|
||||
|
||||
add_filter( 'default_content', array( static::class, 'videopress_video_block_by_guid' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* VideoPress embed shortcode
|
||||
*
|
||||
* Expected input format:
|
||||
* [videopress tLvEwHYZ]
|
||||
*
|
||||
* @param array $atts Shortcode attributes.
|
||||
*
|
||||
* @return string html
|
||||
*/
|
||||
public static function videopress_embed_shortcode( $atts ) {
|
||||
/**
|
||||
* We only accept GUIDs as a first unnamed argument.
|
||||
*/
|
||||
$guid = $atts[0] ?? null;
|
||||
|
||||
/**
|
||||
* Make sure the GUID passed in matches how actual GUIDs are formatted.
|
||||
*/
|
||||
if ( ! videopress_is_valid_guid( $guid ) ) {
|
||||
return '<!-- error: missing or invalid VideoPress video ID -->';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the defaults
|
||||
*/
|
||||
$defaults = array(
|
||||
'w' => 640, // Width of the video player, in pixels
|
||||
'h' => 0, // Height of the video player, in pixels
|
||||
'at' => 0, // How many seconds in to initially seek to
|
||||
'loop' => false, // Whether to loop the video repeatedly
|
||||
'autoplay' => false, // Whether to autoplay the video on load
|
||||
'cover' => true, // Whether to scale the video to its container
|
||||
'muted' => false, // Whether the video should start without sound
|
||||
'controls' => true, // Whether the video should display controls
|
||||
'playsinline' => false, // Whether the video should be allowed to play inline (for browsers that support this)
|
||||
'useaveragecolor' => false, // Whether the video should use the seekbar automatic average color
|
||||
'preloadcontent' => 'metadata',
|
||||
);
|
||||
|
||||
// Make sure "false" will be actually false.
|
||||
foreach ( $atts as $key => $value ) {
|
||||
if ( is_string( $value ) && 'false' === strtolower( $value ) ) {
|
||||
$atts[ $key ] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $atts['preload'] ) && videopress_is_valid_preload( $atts['preload'] ) ) {
|
||||
$atts['preloadcontent'] = $atts['preload'];
|
||||
}
|
||||
|
||||
if ( isset( $atts['preloadcontent'] ) && ! videopress_is_valid_preload( $atts['preloadcontent'] ) ) {
|
||||
unset( $atts['preloadcontent'] );
|
||||
}
|
||||
|
||||
$atts = shortcode_atts( $defaults, $atts, 'videopress' );
|
||||
|
||||
$base_url = 'https://videopress.com/embed/' . $guid;
|
||||
$query_params = array(
|
||||
'at' => $atts['at'],
|
||||
'loop' => $atts['loop'],
|
||||
'autoplay' => $atts['autoplay'],
|
||||
'muted' => $atts['muted'],
|
||||
'controls' => $atts['controls'],
|
||||
'playsinline' => $atts['playsinline'],
|
||||
'useAverageColor' => $atts['useaveragecolor'], // The casing is intentional, shortcode params are lowercase, but player expects useAverageColor
|
||||
'preloadContent' => $atts['preloadcontent'], // The casing is intentional, shortcode params are lowercase, but player expects preloadContent
|
||||
);
|
||||
$src = esc_url( add_query_arg( $query_params, $base_url ) );
|
||||
|
||||
$width = absint( $atts['w'] );
|
||||
if ( ! $atts['h'] ) {
|
||||
$aspect_ratio = 16 / 9; // TODO: Get the correct aspect ratio for the video.
|
||||
$height = $width / $aspect_ratio;
|
||||
} else {
|
||||
$height = absint( $atts['h'] );
|
||||
}
|
||||
|
||||
$cover = $atts['cover'] ? ' data-resize-to-parent="true"' : '';
|
||||
|
||||
$block_template =
|
||||
'<figure class="wp-block-videopress-video wp-block-jetpack-videopress jetpack-videopress-player">' .
|
||||
'<div class="jetpack-videopress-player__wrapper">' .
|
||||
'<iframe ' .
|
||||
'title="' . __( 'VideoPress Video Player', 'jetpack-videopress-pkg' ) . '" ' .
|
||||
'aria-label="' . __( 'VideoPress Video Player', 'jetpack-videopress-pkg' ) . '" ' .
|
||||
'src="%s" ' .
|
||||
'width="%s"' .
|
||||
'height="%s" ' .
|
||||
'frameborder="0" ' .
|
||||
'allowfullscreen%s allow="clipboard-write">' .
|
||||
'</iframe>' .
|
||||
'</div>' .
|
||||
'</figure>';
|
||||
|
||||
$version = Package_Version::PACKAGE_VERSION;
|
||||
Jwt_Token_Bridge::enqueue_jwt_token_bridge();
|
||||
wp_enqueue_script( 'videopress-iframe', 'https://videopress.com/videopress-iframe.js', array(), $version, true );
|
||||
|
||||
return sprintf( $block_template, $src, $width, $height, $cover );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a VideoPress video block content with the given guid
|
||||
*
|
||||
* @param string $content Post content.
|
||||
* @param WP_Post $post Post.
|
||||
* @return string
|
||||
*/
|
||||
public static function videopress_video_block_by_guid( $content, $post ) {
|
||||
if ( isset( $_GET['videopress_guid'] ) && isset( $_GET['_wpnonce'] )
|
||||
&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'videopress-content-nonce' )
|
||||
&& current_user_can( 'edit_post', $post->ID )
|
||||
&& '' === $content
|
||||
) {
|
||||
$guid = sanitize_text_field( wp_unslash( $_GET['videopress_guid'] ) );
|
||||
|
||||
$base_url = 'https://videopress.com/v/' . $guid;
|
||||
$query_params = array(
|
||||
'resizeToParent' => 'true',
|
||||
'cover' => 'true',
|
||||
'preloadContent' => 'metadata',
|
||||
'useAverageColor' => 'true',
|
||||
);
|
||||
$url = esc_url( add_query_arg( $query_params, $base_url ) );
|
||||
|
||||
if ( ! empty( $guid ) ) {
|
||||
// ref /client/lib/url/index.ts
|
||||
$content = '<!-- wp:videopress/video {"guid":"' . $guid . '"} -->
|
||||
<figure class="wp-block-videopress-video wp-block-jetpack-videopress jetpack-videopress-player">
|
||||
<div class="jetpack-videopress-player__wrapper">' . $url . '</div>
|
||||
</figure>
|
||||
<!-- /wp:videopress/video -->';
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the standard video short tag to also process videopress files as well.
|
||||
*
|
||||
* This will parse the given src and, if it is a videopress file, parse as the
|
||||
* VideoPress shortcode instead.
|
||||
*
|
||||
* @param string $html Empty variable to be replaced with shortcode markup.
|
||||
* @param array $attr Attributes of the video shortcode.
|
||||
* @param string $content Video shortcode content.
|
||||
* @param int $instance Unique numeric ID of this video shortcode instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function video_shortcode_override( $html, $attr, $content, $instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$videopress_guid = null;
|
||||
|
||||
if ( isset( $attr['videopress_guid'] ) ) {
|
||||
$videopress_guid = $attr['videopress_guid'];
|
||||
} else {
|
||||
// Handle the different possible url attributes
|
||||
$url_keys = array( 'src', 'mp4' );
|
||||
|
||||
foreach ( $url_keys as $key ) {
|
||||
if ( isset( $attr[ $key ] ) ) {
|
||||
$url = $attr[ $key ];
|
||||
// phpcs:ignore WordPress.WP.CapitalPDangit
|
||||
if ( preg_match( '@videos.(videopress\.com|files\.wordpress\.com)/([a-z0-9]{8})/@i', $url, $matches ) ) {
|
||||
$videopress_guid = $matches[2];
|
||||
}
|
||||
|
||||
// Also test for videopress oembed url, which is used by the Video Media Widget.
|
||||
if ( ! $videopress_guid && preg_match( '@https://videopress.com/v/([a-z0-9]{8})@i', $url, $matches ) ) {
|
||||
$videopress_guid = $matches[1];
|
||||
}
|
||||
|
||||
// Also test for old v.wordpress.com oembed URL.
|
||||
if ( ! $videopress_guid && preg_match( '|^https?://v\.wordpress\.com/([a-zA-Z\d]{8})(.+)?$|i', $url, $matches ) ) { // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText
|
||||
$videopress_guid = $matches[1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $videopress_guid ) {
|
||||
$videopress_atts = array( $videopress_guid );
|
||||
|
||||
// height is ignored on jetpack video block, so we don't pass it for consistency.
|
||||
if ( isset( $attr['width'] ) ) {
|
||||
$videopress_atts['w'] = (int) $attr['width'];
|
||||
}
|
||||
if ( isset( $attr['muted'] ) ) {
|
||||
$videopress_atts['muted'] = $attr['muted'];
|
||||
}
|
||||
if ( isset( $attr['autoplay'] ) ) {
|
||||
$videopress_atts['autoplay'] = $attr['autoplay'];
|
||||
}
|
||||
if ( isset( $attr['loop'] ) ) {
|
||||
$videopress_atts['loop'] = $attr['loop'];
|
||||
}
|
||||
// The core video block doesn't support the cover attribute, setting it to false for consistency.
|
||||
$videopress_atts['cover'] = false;
|
||||
|
||||
// Then display the VideoPress version of the stored GUID!
|
||||
return self::videopress_embed_shortcode( $videopress_atts );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Extensions
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* VideoPress Extensions class.
|
||||
*/
|
||||
class Block_Editor_Extensions {
|
||||
/**
|
||||
* What version of the blocks we are loading.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $blocks_variation = 'production';
|
||||
|
||||
/**
|
||||
* Script handle
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $script_handle = '';
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*
|
||||
* This method should be called only once by the Block registrar.
|
||||
* Do not call this method again.
|
||||
*
|
||||
* @param string $script_handle - The script handle.
|
||||
*/
|
||||
public static function init( $script_handle ) {
|
||||
if ( ! Status::is_registrant_plugin_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the videopress/video editor script handle to localize enqueue scripts.
|
||||
* @see https://developer.wordpress.org/reference/functions/generate_block_asset_handle
|
||||
*/
|
||||
self::$script_handle = $script_handle;
|
||||
|
||||
/**
|
||||
* Alternative to `JETPACK_BETA_BLOCKS`, set to `true` to load Beta Blocks.
|
||||
*
|
||||
* @since 6.9.0
|
||||
* @deprecated Jetpack 11.8.0 Use jetpack_blocks_variation filter instead.
|
||||
*
|
||||
* @param boolean
|
||||
*/
|
||||
if (
|
||||
apply_filters_deprecated(
|
||||
'jetpack_load_beta_blocks',
|
||||
array( false ),
|
||||
'jetpack-11.8.0',
|
||||
'jetpack_blocks_variation'
|
||||
)
|
||||
) {
|
||||
self::$blocks_variation = 'beta';
|
||||
}
|
||||
|
||||
/*
|
||||
* Get block variation, from the new constant or the old one.
|
||||
*/
|
||||
if ( Constants::is_true( 'JETPACK_BETA_BLOCKS' ) ) {
|
||||
self::$blocks_variation = 'beta';
|
||||
}
|
||||
|
||||
$blocks_variation = Constants::get_constant( 'JETPACK_BLOCKS_VARIATION' );
|
||||
if ( ! empty( $blocks_variation ) ) {
|
||||
/**
|
||||
* Allow customizing the variation of blocks in use on a site.
|
||||
* Overwrites any previously set values, whether by constant or filter.
|
||||
*
|
||||
* @since Jetpack 8.1.0
|
||||
*
|
||||
* @param string $block_variation Can be beta, experimental, and production. Defaults to production.
|
||||
*/
|
||||
self::$blocks_variation = apply_filters( 'jetpack_blocks_variation', $blocks_variation );
|
||||
}
|
||||
|
||||
// Register the script.
|
||||
add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_extensions' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extensions list
|
||||
*
|
||||
* @return array The extensions list.
|
||||
*/
|
||||
public static function get_list() {
|
||||
$videopress_extensions_file = __DIR__ . '/../build/block-editor/extensions/index.json';
|
||||
$videopress_extensions_file_exists = file_exists( $videopress_extensions_file );
|
||||
if ( ! $videopress_extensions_file_exists ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$videopress_extensions_data = (array) json_decode(
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
file_get_contents( $videopress_extensions_file )
|
||||
);
|
||||
|
||||
$extensions_list = array_map(
|
||||
function ( $extension ) {
|
||||
return array(
|
||||
'name' => $extension,
|
||||
'isBeta' => true,
|
||||
'isEnabled' => 'beta' === self::$blocks_variation,
|
||||
);
|
||||
},
|
||||
$videopress_extensions_data['beta']
|
||||
);
|
||||
|
||||
return $extensions_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the extension is available
|
||||
*
|
||||
* @param string $slug The extension slug.
|
||||
* @return boolean True if the extension is available, false otherwise.
|
||||
*/
|
||||
public static function is_extension_available( $slug ) {
|
||||
$extensions_list = self::get_list();
|
||||
foreach ( $extensions_list as $extension ) {
|
||||
if ( $extension['name'] === $slug ) {
|
||||
return $extension['isEnabled'];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the extensions script.
|
||||
*/
|
||||
public static function enqueue_extensions() {
|
||||
$extensions_list = self::get_list();
|
||||
|
||||
$site_type = 'jetpack';
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
$site_type = 'simple';
|
||||
} elseif ( ( new Host() )->is_woa_site() ) {
|
||||
$site_type = 'atomic';
|
||||
}
|
||||
|
||||
$videopress_editor_state = array(
|
||||
'extensions' => $extensions_list,
|
||||
'siteType' => $site_type,
|
||||
'myJetpackConnectUrl' => admin_url( 'admin.php?page=my-jetpack#/connection' ),
|
||||
'jetpackVideoPressSettingUrl' => admin_url( 'admin.php?page=jetpack#/settings?term=videopress' ),
|
||||
'isVideoPressModuleActive' => Status::is_jetpack_plugin_and_videopress_module_active(),
|
||||
'isStandaloneActive' => Status::is_standalone_plugin_active(),
|
||||
'imagesURLBase' => plugin_dir_url( __DIR__ ) . 'build/images/',
|
||||
'playerBridgeUrl' => plugins_url( '../build/lib/player-bridge.js', __FILE__ ),
|
||||
);
|
||||
|
||||
// Expose initial state of site connection
|
||||
Connection_Initial_State::render_script( self::$script_handle );
|
||||
|
||||
// Expose initial state of videoPress editor
|
||||
wp_localize_script( self::$script_handle, 'videoPressEditorState', $videopress_editor_state );
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Media block replacement class.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
**/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* Class Block_Replacement
|
||||
**/
|
||||
class Block_Replacement {
|
||||
/**
|
||||
* Whether the class has been initiated.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $initiated = false;
|
||||
|
||||
/**
|
||||
* Initialize replacement.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( self::$initiated ) {
|
||||
return;
|
||||
}
|
||||
add_filter( 'render_block', array( self::class, 'replace_media_text_with_videopress' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace video in Media & Text block with Videopress shortcode.
|
||||
*
|
||||
* @param string $block_content The block content.
|
||||
* @param array $block The block.
|
||||
* @return string
|
||||
*/
|
||||
public static function replace_media_text_with_videopress( $block_content, $block ) {
|
||||
if ( isset( $block['blockName'] ) && $block['blockName'] === 'core/media-text' ) {
|
||||
|
||||
// Make sure we have a $post_id that could be valid; if we don't, then video_get_info_by_blogpostid()
|
||||
// will fail, so there's no point in calling it.
|
||||
$post_id = isset( $block['attrs']['mediaId'] ) ? (int) $block['attrs']['mediaId'] : 0;
|
||||
if ( $post_id <= 0 ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
$video_info = video_get_info_by_blogpostid( get_current_blog_id(), $post_id );
|
||||
if ( $video_info && $video_info->guid ) {
|
||||
$videopress_shortcode = sprintf( '[videopress %s]', esc_attr( $video_info->guid ) );
|
||||
$block_content = preg_replace( '/<video.*?<\/video>/is', do_shortcode( $videopress_shortcode ), $block_content );
|
||||
}
|
||||
}
|
||||
return $block_content;
|
||||
}
|
||||
}
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
/**
|
||||
* The Data class.
|
||||
* This class provides methods for data VideoPress access and manipulation.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use WP_REST_Request;
|
||||
/**
|
||||
* The Data class.
|
||||
*/
|
||||
class Data {
|
||||
|
||||
/**
|
||||
* Gets the Jetpack blog ID
|
||||
*
|
||||
* @return int The blog ID
|
||||
*/
|
||||
public static function get_blog_id() {
|
||||
return VideoPressToken::blog_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the VideoPress site privacy configuration.
|
||||
*
|
||||
* @return boolean If all the videos are private on the site
|
||||
*/
|
||||
public static function get_videopress_videos_private_for_site() {
|
||||
/**
|
||||
* If it's a Simple site, returns the site privacy setting.
|
||||
*/
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
return video_is_private_wpcom_blog( get_current_blog_id() );
|
||||
}
|
||||
/**
|
||||
* If it's a private Atomic site, the default setting is private as well.
|
||||
*/
|
||||
if ( ( new Host() )->is_woa_site() ) {
|
||||
if ( ( intval( get_option( 'blog_public', '' ) ) === -1 ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* If it's a Jetpack site or a public Atomic site, check the settings */
|
||||
return boolval( get_option( 'videopress_private_enabled_for_site', false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the VideoPress Settings.
|
||||
*
|
||||
* @return array The settings as an associative array.
|
||||
*/
|
||||
public static function get_videopress_settings() {
|
||||
$site_type = 'jetpack';
|
||||
$site_is_private = false;
|
||||
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
$site_type = 'simple';
|
||||
$site_is_private = video_is_private_wpcom_blog( get_current_blog_id() );
|
||||
} elseif ( ( new Host() )->is_woa_site() ) {
|
||||
$site_type = 'atomic';
|
||||
$site_is_private = intval( get_option( 'blog_public', '' ) ) === -1;
|
||||
}
|
||||
|
||||
return array(
|
||||
'videopress_videos_private_for_site' => self::get_videopress_videos_private_for_site(),
|
||||
'site_is_private' => $site_is_private,
|
||||
'site_type' => $site_type,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the video data
|
||||
*
|
||||
* @param boolean $is_videopress - True when getting VideoPress data.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_video_data( $is_videopress = true ) {
|
||||
$video_data = array(
|
||||
'videos' => array(),
|
||||
'total' => 0,
|
||||
'totalPages' => 0,
|
||||
'query' => array(
|
||||
'order' => 'desc',
|
||||
'orderBy' => 'date',
|
||||
'itemsPerPage' => 6,
|
||||
'page' => 1,
|
||||
),
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'order' => $video_data['query']['order'],
|
||||
'orderby' => $video_data['query']['orderBy'],
|
||||
'per_page' => $video_data['query']['itemsPerPage'],
|
||||
'page' => $video_data['query']['page'],
|
||||
);
|
||||
|
||||
if ( $is_videopress ) {
|
||||
$args['mime_type'] = 'video/videopress';
|
||||
} else {
|
||||
$args['media_type'] = 'video';
|
||||
$args['no_videopress'] = true;
|
||||
}
|
||||
|
||||
// Do an internal request for the media list
|
||||
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
|
||||
$request->set_query_params( $args );
|
||||
$response = rest_do_request( $request );
|
||||
|
||||
if ( $response->is_error() ) {
|
||||
// @todo: error handling
|
||||
return $video_data;
|
||||
}
|
||||
|
||||
// load the real values
|
||||
$video_data['videos'] = $response->get_data();
|
||||
$headers = $response->get_headers();
|
||||
|
||||
if ( isset( $headers['X-WP-Total'] ) ) {
|
||||
$video_data['total'] = $headers['X-WP-Total'];
|
||||
}
|
||||
|
||||
if ( isset( $headers['X-WP-TotalPages'] ) ) {
|
||||
$video_data['totalPages'] = $headers['X-WP-TotalPages'];
|
||||
}
|
||||
|
||||
return $video_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_user_data() {
|
||||
$user_data = array(
|
||||
'items' => array(),
|
||||
'pagination' => array(
|
||||
'total' => 0,
|
||||
'totalPages' => 1,
|
||||
),
|
||||
'query' => array(
|
||||
'order' => 'asc',
|
||||
'orderBy' => 'name',
|
||||
),
|
||||
'_meta' => array(
|
||||
'relyOnInitialState' => true,
|
||||
),
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'order' => $user_data['query']['order'],
|
||||
'orderby' => $user_data['query']['orderBy'],
|
||||
);
|
||||
|
||||
// Do an internal request for the user list
|
||||
$request = new WP_REST_Request( 'GET', '/wp/v2/users' );
|
||||
$request->set_query_params( $args );
|
||||
$response = rest_do_request( $request );
|
||||
|
||||
if ( $response->is_error() ) {
|
||||
// @todo: error handling
|
||||
return $user_data;
|
||||
}
|
||||
|
||||
// load the real values
|
||||
$user_data['items'] = $response->get_data();
|
||||
$headers = $response->get_headers();
|
||||
|
||||
if ( isset( $headers['X-WP-Total'] ) ) {
|
||||
$user_data['pagination']['total'] = $headers['X-WP-Total'];
|
||||
}
|
||||
|
||||
if ( isset( $headers['X-WP-TotalPages'] ) ) {
|
||||
$user_data['pagination']['totalPages'] = $headers['X-WP-TotalPages'];
|
||||
}
|
||||
|
||||
return $user_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the VideoPress used storage space in bytes
|
||||
*
|
||||
* @return int the used storage space
|
||||
*/
|
||||
public static function get_storage_used() {
|
||||
$site_data = Site::get_site_info();
|
||||
if ( is_wp_error( $site_data ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( isset( $site_data['options'] ) && isset( $site_data['options']['videopress_storage_used'] ) ) {
|
||||
return intval( round( $site_data['options']['videopress_storage_used'] * 1024 * 1024 ) );
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the initial state that depends on a valid site connection
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_connected_initial_state() {
|
||||
return array(
|
||||
'videos' => array(
|
||||
'storageUsed' => self::get_storage_used(),
|
||||
),
|
||||
'purchases' => array(
|
||||
'items' => Site::get_purchases(),
|
||||
'isFetching' => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the site has as connected owner
|
||||
*/
|
||||
public static function has_connected_owner() {
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ( new Connection_Manager() )->has_connected_owner() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is able to perform actions that modify data
|
||||
*/
|
||||
public static function can_perform_action() {
|
||||
if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$connection = new Connection_Manager();
|
||||
|
||||
return (
|
||||
$connection->is_connected() &&
|
||||
self::has_connected_owner() &&
|
||||
$connection->is_user_connected()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial state of the VideoPress app,
|
||||
* used to render initially the app in the frontend.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_initial_state() {
|
||||
$videopress_data = self::get_video_data();
|
||||
$local_videos_data = self::get_video_data( false );
|
||||
|
||||
// Tweak local videos data.
|
||||
$local_videos = array_map(
|
||||
function ( $video ) {
|
||||
$video = (array) $video;
|
||||
$id = $video['id'];
|
||||
$media_details = $video['media_details'];
|
||||
$jetpack_videopress = $video['jetpack_videopress'];
|
||||
$read_error = null;
|
||||
|
||||
// In malformed files, the media_details or jetpack_videopress properties are not arrays.
|
||||
if ( ! is_array( $media_details ) || ! is_array( $jetpack_videopress ) ) {
|
||||
$media_details = (array) $media_details;
|
||||
$jetpack_videopress = (array) $jetpack_videopress;
|
||||
$read_error = Upload_Exception::ERROR_MALFORMED_FILE;
|
||||
}
|
||||
|
||||
// Check if video is already uploaded to VideoPress or has some error.
|
||||
try {
|
||||
$uploader = new Uploader( $id );
|
||||
$is_uploaded_to_videopress = $uploader->is_uploaded();
|
||||
} catch ( Upload_Exception $e ) {
|
||||
$is_uploaded_to_videopress = false;
|
||||
$read_error = $e->getCode();
|
||||
}
|
||||
|
||||
$upload_date = $video['date'];
|
||||
$url = $video['source_url'];
|
||||
|
||||
$title = $jetpack_videopress['title'];
|
||||
$description = $jetpack_videopress['description'];
|
||||
$caption = $jetpack_videopress['caption'];
|
||||
|
||||
$width = $media_details['width'] ?? null;
|
||||
$height = $media_details['height'] ?? null;
|
||||
$duration = $media_details['length'] ?? null;
|
||||
|
||||
return array(
|
||||
'id' => $id,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'caption' => $caption,
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'url' => $url,
|
||||
'uploadDate' => $upload_date,
|
||||
'duration' => $duration,
|
||||
'isUploadedToVideoPress' => $is_uploaded_to_videopress,
|
||||
'readError' => $read_error,
|
||||
);
|
||||
},
|
||||
$local_videos_data['videos']
|
||||
);
|
||||
|
||||
// Tweak VideoPress videos data.
|
||||
$videos = array_map(
|
||||
array( __CLASS__, 'prepare_videopress_video_data' ),
|
||||
$videopress_data['videos']
|
||||
);
|
||||
|
||||
$site_settings = self::get_videopress_settings();
|
||||
|
||||
$initial_state = array(
|
||||
'users' => self::get_user_data(),
|
||||
'siteSettings' => array(
|
||||
'videoPressVideosPrivateForSite' => $site_settings['videopress_videos_private_for_site'],
|
||||
'siteIsPrivate' => $site_settings['site_is_private'],
|
||||
'siteType' => $site_settings['site_type'],
|
||||
),
|
||||
'videos' => array(
|
||||
'uploadedVideoCount' => $videopress_data['total'],
|
||||
'items' => $videos,
|
||||
'isFetching' => false,
|
||||
'isFetchingUploadedVideoCount' => false,
|
||||
'pagination' => array(
|
||||
'totalPages' => $videopress_data['totalPages'],
|
||||
'total' => $videopress_data['total'],
|
||||
),
|
||||
'query' => $videopress_data['query'],
|
||||
'_meta' => array(
|
||||
'processedAllVideosBeingRemoved' => true,
|
||||
'relyOnInitialState' => true,
|
||||
),
|
||||
),
|
||||
'localVideos' => array(
|
||||
'uploadedVideoCount' => $local_videos_data['total'],
|
||||
'items' => $local_videos,
|
||||
'isFetching' => false,
|
||||
'isFetchingUploadedVideoCount' => false,
|
||||
'pagination' => array(
|
||||
'totalPages' => $local_videos_data['totalPages'],
|
||||
'total' => $local_videos_data['total'],
|
||||
),
|
||||
'query' => $local_videos_data['query'],
|
||||
'_meta' => array(
|
||||
'relyOnInitialState' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if ( self::has_connected_owner() ) {
|
||||
return array_merge_recursive( $initial_state, self::get_connected_initial_state() );
|
||||
}
|
||||
|
||||
return $initial_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a single VideoPress video, as returned by the media REST endpoint, to
|
||||
* the shape consumed by the VideoPress dashboard app.
|
||||
*
|
||||
* Videos that are still processing (or otherwise have partial metadata) come
|
||||
* back without `videopress`, `width` or `height` in their media_details, so
|
||||
* every nested access is coalesced to avoid undefined-key and offset-on-null
|
||||
* warnings.
|
||||
*
|
||||
* @param array|object $video A single video entry from the media REST endpoint.
|
||||
* @return array The video data formatted for the dashboard app.
|
||||
*/
|
||||
private static function prepare_videopress_video_data( $video ) {
|
||||
$video = (array) $video;
|
||||
$id = $video['id'];
|
||||
$guid = $video['jetpack_videopress_guid'];
|
||||
$media_details = (array) $video['media_details'];
|
||||
$jetpack_videopress = (array) $video['jetpack_videopress'];
|
||||
|
||||
$videopress_media_details = $media_details['videopress'] ?? array();
|
||||
$width = $media_details['width'] ?? null;
|
||||
$height = $media_details['height'] ?? null;
|
||||
|
||||
$title = $jetpack_videopress['title'];
|
||||
$description = $jetpack_videopress['description'];
|
||||
$caption = $jetpack_videopress['caption'];
|
||||
$rating = $jetpack_videopress['rating'];
|
||||
$allow_download = $jetpack_videopress['allow_download'];
|
||||
$display_embed = $jetpack_videopress['display_embed'];
|
||||
$privacy_setting = $jetpack_videopress['privacy_setting'];
|
||||
$needs_playback_token = $jetpack_videopress['needs_playback_token'];
|
||||
$is_private = $jetpack_videopress['is_private'];
|
||||
|
||||
$original = $videopress_media_details['original'] ?? null;
|
||||
$poster = ( ! $needs_playback_token ) ? ( $videopress_media_details['poster'] ?? null ) : null;
|
||||
$upload_date = $videopress_media_details['upload_date'] ?? null;
|
||||
$duration = $videopress_media_details['duration'] ?? null;
|
||||
$file_url_base = $videopress_media_details['file_url_base'] ?? null;
|
||||
$finished = $videopress_media_details['finished'] ?? null;
|
||||
$files = $videopress_media_details['files'] ?? null;
|
||||
$filename = $original !== null ? basename( $original ) : null;
|
||||
|
||||
if ( isset( $files['dvd']['original_img'] ) && isset( $file_url_base['https'] ) && $privacy_setting !== 1 ) {
|
||||
$thumbnail = $file_url_base['https'] . $files['dvd']['original_img'];
|
||||
} else {
|
||||
$thumbnail = null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => $id,
|
||||
'guid' => $guid,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'caption' => $caption,
|
||||
'url' => $original,
|
||||
'uploadDate' => $upload_date,
|
||||
'duration' => $duration,
|
||||
'isPrivate' => $is_private,
|
||||
'posterImage' => $poster,
|
||||
'allowDownload' => $allow_download,
|
||||
'displayEmbed' => $display_embed,
|
||||
'rating' => $rating,
|
||||
'privacySetting' => $privacy_setting,
|
||||
'needsPlaybackToken' => $needs_playback_token,
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'poster' => array(
|
||||
'src' => $poster,
|
||||
),
|
||||
'thumbnail' => $thumbnail,
|
||||
'finished' => $finished,
|
||||
'filename' => $filename,
|
||||
);
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Divi integration for Videopress.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
**/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\VideoPress\Divi5\Divi_5;
|
||||
|
||||
/**
|
||||
* Class Divi
|
||||
**/
|
||||
class Divi {
|
||||
/**
|
||||
* The instance.
|
||||
*
|
||||
* @var Divi
|
||||
**/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Running or not.
|
||||
*
|
||||
* @var bool
|
||||
**/
|
||||
private $running = false;
|
||||
|
||||
/**
|
||||
* VideoPress Divi Extension object.
|
||||
*
|
||||
* @var ?\VideoPress_Divi_Extension
|
||||
**/
|
||||
private $vidi_extension;
|
||||
|
||||
/**
|
||||
* Initializes VideoPress/Divi integration.
|
||||
*
|
||||
* Called only once by the Initializer class
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function init() {
|
||||
return self::get_instance()->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the extension.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function run() {
|
||||
if ( $this->running ) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->running = true;
|
||||
add_action( 'divi_extensions_init', array( $this, 'initialize_extension' ) );
|
||||
add_action( 'et_fb_framework_loaded', array( $this, 'on_fb_framework_loaded' ) );
|
||||
|
||||
// Divi 5 integration. These hooks only fire under Divi 5; the legacy
|
||||
// extension above continues to serve Divi 4.
|
||||
Divi_5::init();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the Frontend Builder is loaded.
|
||||
*/
|
||||
public function on_fb_framework_loaded() {
|
||||
Jwt_Token_Bridge::enqueue_jwt_token_bridge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the extension's main class instance.
|
||||
*/
|
||||
public function initialize_extension() {
|
||||
require_once plugin_dir_path( __FILE__ ) . 'videopress-divi/class-videopress-divi-extension.php';
|
||||
$this->vidi_extension = new \VideoPress_Divi_Extension();
|
||||
}
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
/**
|
||||
* The modernized VideoPress dashboard React initial state.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\My_Jetpack\Product;
|
||||
use Automattic\Jetpack\My_Jetpack\Products as My_Jetpack_Products;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
use Jetpack_Options;
|
||||
use function admin_url;
|
||||
use function esc_url_raw;
|
||||
use function get_bloginfo;
|
||||
use function get_locale;
|
||||
use function get_option;
|
||||
use function get_site_url;
|
||||
use function plugins_url;
|
||||
use function rest_url;
|
||||
use function wp_add_inline_script;
|
||||
use function wp_create_nonce;
|
||||
use function wp_json_encode;
|
||||
use function wp_parse_url;
|
||||
use function wp_script_is;
|
||||
|
||||
/**
|
||||
* The modernized VideoPress dashboard React initial state.
|
||||
*
|
||||
* Mirrors the Activity Log dashboard pattern: a small structured payload
|
||||
* inlined as `var JPVIDEOPRESS_INITIAL_STATE=...` before the wp-build boot
|
||||
* script runs. Phases 6 and 8 will consume the payload via per-file
|
||||
* `declare const` ambient types.
|
||||
*/
|
||||
class Initial_State {
|
||||
|
||||
const SCRIPT_HANDLE = 'jetpack-videopress-dashboard-wp-admin-prerequisites';
|
||||
|
||||
/**
|
||||
* WPCOM product slug purchased by the dashboard's upgrade CTA.
|
||||
*
|
||||
* Mirrors the product `Plan::get_product()` resolves to
|
||||
* (`$products->jetpack_videopress`). Kept as a constant rather than read
|
||||
* from `Plan::get_product()` because that helper issues a synchronous
|
||||
* WPCOM request, which we don't want to incur on every page render.
|
||||
*/
|
||||
const VIDEOPRESS_PRODUCT_SLUG = 'jetpack_videopress';
|
||||
|
||||
/**
|
||||
* Register the inline-state enqueue hook.
|
||||
*
|
||||
* Hooks `admin_enqueue_scripts` at priority 11 so the wp-build page
|
||||
* (`build/pages/jetpack-videopress-dashboard/page-wp-admin.php`) has
|
||||
* already registered the prerequisites handle at its default priority 10.
|
||||
* The `wp_script_is( ..., 'registered' )` check inside the callback
|
||||
* doubles as the page guard.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ), 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline the initial-state payload before the wp-build boot script.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue() {
|
||||
if ( ! Admin_UI::is_modernized() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_script_is( self::SCRIPT_HANDLE, 'registered' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_add_inline_script( self::SCRIPT_HANDLE, ( new self() )->render(), 'before' );
|
||||
|
||||
// Hydrate the JP connection store (`window.JP_CONNECTION_INITIAL_STATE`)
|
||||
// so shared connection-aware hooks — notably
|
||||
// `useProductCheckoutWorkflow`, which powers the upgrade CTA — work on
|
||||
// the modernized dashboard exactly as they do on the legacy one. This
|
||||
// also sets `window.jpTracksContext.blog_id` for `@automattic/jetpack-analytics`.
|
||||
Connection_Initial_State::render_script( self::SCRIPT_HANDLE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial state data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_data() {
|
||||
$gmt_offset = get_option( 'gmt_offset' );
|
||||
$timezone_string = get_option( 'timezone_string' );
|
||||
$home_host = wp_parse_url( get_site_url(), PHP_URL_HOST );
|
||||
|
||||
return array(
|
||||
'API' => array(
|
||||
'WP_API_root' => esc_url_raw( rest_url() ),
|
||||
'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'contentNonce' => wp_create_nonce( 'videopress-content-nonce' ),
|
||||
),
|
||||
'jetpackStatus' => array(
|
||||
'calypsoSlug' => ( new Status() )->get_site_suffix(),
|
||||
),
|
||||
'product' => array(
|
||||
// Fed to `useProductCheckoutWorkflow` as the product to purchase.
|
||||
'slug' => self::VIDEOPRESS_PRODUCT_SLUG,
|
||||
),
|
||||
'siteData' => array(
|
||||
'id' => Jetpack_Options::get_option( 'id' ),
|
||||
'title' => get_bloginfo( 'name' ) ? get_bloginfo( 'name' ) : get_site_url(),
|
||||
'adminUrl' => esc_url_raw( admin_url() ),
|
||||
'slug' => is_string( $home_host ) ? $home_host : '',
|
||||
'gmtOffset' => is_numeric( $gmt_offset ) ? (float) $gmt_offset : 0.0,
|
||||
'timezoneString' => is_string( $timezone_string ) ? $timezone_string : '',
|
||||
'locale' => str_replace( '_', '-', (string) get_locale() ),
|
||||
// Paid-tier capability check. Drives the free-tier UX (callout /
|
||||
// DataViews configuration) once designer input lands. Backed by
|
||||
// `Product::get_site_features_from_wpcom()`, which caches the WPCOM
|
||||
// `/sites/%d/features` response in a 15-second site transient. On a
|
||||
// cache miss this issues a synchronous WPCOM request that blocks
|
||||
// page rendering until it returns.
|
||||
'hasVideoPressAccess' => self::has_videopress_access(),
|
||||
'isVideoPress1TB' => self::has_videopress_feature( 'videopress-1tb-storage' ),
|
||||
'isVideoPressUnlimited' => self::has_videopress_feature( 'videopress-unlimited-storage' ),
|
||||
),
|
||||
'assets' => array(
|
||||
'buildUrl' => plugins_url( '../build/', __FILE__ ),
|
||||
),
|
||||
// Authoritative map of accepted upload types (extension => mimetype),
|
||||
// so the dashboard's drag-and-drop filter accepts exactly what the
|
||||
// VideoPress backend supports rather than guessing client-side.
|
||||
'allowedVideoExtensions' => Admin_UI::get_allowed_video_extensions(),
|
||||
// Product/pricing payload for the pre-connection upsell. Only
|
||||
// populated when the site isn't connected — the only time the
|
||||
// connection gate renders the upsell — so connected dashboards never
|
||||
// incur the synchronous WPCOM pricing request `get_pricing_data()`
|
||||
// makes.
|
||||
'pricing' => ( new Connection_Manager() )->is_connected() ? null : $this->get_pricing_data(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Product description, feature list, and yearly price for the
|
||||
* pre-connection upsell (a port of the legacy dashboard's pricing table).
|
||||
*
|
||||
* Backed by `Plan::get_product_price()`, which issues a synchronous WPCOM
|
||||
* request, so this only runs for disconnected sites. Returns null when the
|
||||
* product or price data isn't available (e.g. the WPCOM request fails), in
|
||||
* which case the gate falls back to the plain connect screen.
|
||||
*
|
||||
* @return array|null The upsell payload, or null when it can't be built.
|
||||
*/
|
||||
private function get_pricing_data() {
|
||||
$site_product = My_Jetpack_Products::get_product( 'videopress' );
|
||||
$product_price = Plan::get_product_price();
|
||||
|
||||
if ( ! is_array( $site_product ) || ! isset( $product_price['yearly'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => isset( $site_product['description'] ) ? (string) $site_product['description'] : '',
|
||||
'features' => isset( $site_product['features'] ) ? array_values( (array) $site_product['features'] ) : array(),
|
||||
'yearly' => $product_price['yearly'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the site has any paid VideoPress feature flag active.
|
||||
*
|
||||
* Matches the active-features check used by the existing
|
||||
* `videopress/v1/features` REST endpoint: any of the storage tiers
|
||||
* counts as paid access. On WPCOM the legacy `videopress` slug also
|
||||
* grants access.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_videopress_access() {
|
||||
return self::has_videopress_feature( 'videopress-1tb-storage' )
|
||||
|| self::has_videopress_feature( 'videopress-unlimited-storage' )
|
||||
|| ( ( new Host() )->is_wpcom_platform() && self::has_videopress_feature( 'videopress' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the named feature appears in the WPCOM active-features list.
|
||||
*
|
||||
* @param string $feature_slug Feature slug as returned by WPCOM (e.g. `videopress-1tb-storage`).
|
||||
* @return bool
|
||||
*/
|
||||
private static function has_videopress_feature( $feature_slug ) {
|
||||
$features = Product::get_site_features_from_wpcom();
|
||||
|
||||
if ( is_wp_error( $features ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$active = $features['active'] ?? array();
|
||||
|
||||
return in_array( $feature_slug, $active, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the initial state into a JavaScript variable.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render() {
|
||||
return 'var JPVIDEOPRESS_INITIAL_STATE=' . wp_json_encode( $this->get_data(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';';
|
||||
}
|
||||
}
|
||||
+514
@@ -0,0 +1,514 @@
|
||||
<?php
|
||||
/**
|
||||
* The initializer class for the videopress package
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* Initialized the VideoPress package
|
||||
*/
|
||||
class Initializer {
|
||||
|
||||
const JETPACK_VIDEOPRESS_IFRAME_API_HANDLER = 'jetpack-videopress-iframe-api';
|
||||
|
||||
/**
|
||||
* Initialization optinos
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $init_options = array();
|
||||
|
||||
/**
|
||||
* Initializes the VideoPress package
|
||||
*
|
||||
* This method is called by Config::ensure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
if ( ! did_action( 'videopress_init' ) ) {
|
||||
|
||||
self::unconditional_initialization();
|
||||
|
||||
if ( Status::is_active() ) {
|
||||
self::active_initialization();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after the VideoPress package is initialized
|
||||
*
|
||||
* @since 0.1.1
|
||||
*/
|
||||
do_action( 'videopress_init' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the initialization options
|
||||
*
|
||||
* This method is called by the Config class
|
||||
*
|
||||
* @param array $options The initialization options.
|
||||
* @return void
|
||||
*/
|
||||
public static function update_init_options( array $options ) {
|
||||
if ( empty( $options['admin_ui'] ) || self::should_initialize_admin_ui() ) { // do not overwrite if already set to true.
|
||||
return;
|
||||
}
|
||||
|
||||
self::$init_options['admin_ui'] = $options['admin_ui'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the initialization options and returns whether the admin_ui should be initialized or not
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function should_initialize_admin_ui() {
|
||||
return isset( self::$init_options['admin_ui'] ) && true === self::$init_options['admin_ui'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize VideoPress features that should be initialized whenever VideoPress is present, even if the module is not active
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function unconditional_initialization() {
|
||||
if ( self::should_include_utilities() ) {
|
||||
require_once __DIR__ . '/utility-functions.php';
|
||||
}
|
||||
|
||||
// Set up package version hook.
|
||||
add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\Package_Version::send_package_version_to_tracker' );
|
||||
|
||||
Module_Control::init();
|
||||
|
||||
/*
|
||||
* The WPCOM REST API v2 endpoints only register routes/fields on REST
|
||||
* init, so defer constructing them (and autoloading their classes) until
|
||||
* a REST request is actually served. Registered on both REST init hooks
|
||||
* so the routes remain available in every context they were before, and
|
||||
* guarded so the endpoints are instantiated only once per request.
|
||||
*/
|
||||
$register_rest_api_v2_endpoints = static function () {
|
||||
static $registered = false;
|
||||
if ( $registered ) {
|
||||
return;
|
||||
}
|
||||
$registered = true;
|
||||
new WPCOM_REST_API_V2_Endpoint_VideoPress();
|
||||
new WPCOM_REST_API_V2_Attachment_VideoPress_Field();
|
||||
new WPCOM_REST_API_V2_Attachment_VideoPress_Data();
|
||||
};
|
||||
add_action( 'rest_api_init', $register_rest_api_v2_endpoints, 0 );
|
||||
add_action( 'restapi_theme_init', $register_rest_api_v2_endpoints, 0 );
|
||||
|
||||
if ( is_admin() ) {
|
||||
AJAX::init();
|
||||
} else {
|
||||
require_once __DIR__ . '/class-block-replacement.php';
|
||||
Block_Replacement::init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This avoids conflicts when running VideoPress plugin with older versions of the Jetpack plugin
|
||||
*
|
||||
* On version 11.3-a.7 utility functions include were removed from the plugin and it is safe to include it from the package
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private static function should_include_utilities() {
|
||||
if ( ! class_exists( 'Jetpack' ) || ! defined( 'JETPACK__VERSION' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return version_compare( JETPACK__VERSION, '11.3-a.7', '>=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize VideoPress features that should be initialized only when the module is active
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function active_initialization() {
|
||||
Attachment_Handler::init();
|
||||
Jwt_Token_Bridge::init();
|
||||
Initial_State::init();
|
||||
XMLRPC::init();
|
||||
Block_Editor_Content::init();
|
||||
|
||||
/*
|
||||
* These endpoints only add their routes on REST init, so defer calling
|
||||
* init() (and autoloading the endpoint classes) until a REST request is
|
||||
* served. Priority 0 ensures the routes still register before the
|
||||
* default-priority rest_api_init handlers run. Class-name strings are
|
||||
* used so the classes are not autoloaded on non-REST requests.
|
||||
*/
|
||||
foreach (
|
||||
array(
|
||||
Uploader_Rest_Endpoints::class,
|
||||
Rest_Controller::class,
|
||||
VideoPress_Rest_Api_V1_Stats::class,
|
||||
VideoPress_Rest_Api_V1_Site::class,
|
||||
VideoPress_Rest_Api_V1_Settings::class,
|
||||
VideoPress_Rest_Api_V1_Features::class,
|
||||
) as $rest_endpoint
|
||||
) {
|
||||
add_action( 'rest_api_init', array( $rest_endpoint, 'init' ), 0 );
|
||||
}
|
||||
self::register_oembed_providers();
|
||||
|
||||
// Enqueuethe VideoPress Iframe API script in the front-end.
|
||||
add_filter( 'embed_oembed_html', array( __CLASS__, 'enqueue_videopress_iframe_api_script' ), 10, 4 );
|
||||
|
||||
if ( self::should_initialize_admin_ui() ) {
|
||||
Admin_UI::init();
|
||||
}
|
||||
|
||||
Divi::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly register VideoPress oembed provider for patterns not supported by core
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_oembed_providers() {
|
||||
$host = rawurlencode( home_url() );
|
||||
// videopress.com/v is already registered in core.
|
||||
// By explicitly declaring the provider here, we can speed things up by not relying on oEmbed discovery.
|
||||
wp_oembed_add_provider( '#^https?://video.wordpress.com/v/.*#', 'https://public-api.wordpress.com/oembed/?for=' . $host, true );
|
||||
// This is needed as it's not supported in oEmbed discovery
|
||||
wp_oembed_add_provider( '|^https?://v\.wordpress\.com/([a-zA-Z\d]{8})(.+)?$|i', 'https://public-api.wordpress.com/oembed/?for=' . $host, true ); // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText
|
||||
|
||||
add_filter( 'embed_oembed_html', array( __CLASS__, 'video_enqueue_bridge_when_oembed_present' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues VideoPress token bridge when a VideoPress oembed is present on the current page.
|
||||
*
|
||||
* @param string|false $cache The cached HTML result, stored in post meta.
|
||||
* @param string $url The attempted embed URL.
|
||||
* @param array $attr An array of shortcode attributes.
|
||||
* @param int $post_ID Post ID.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public static function video_enqueue_bridge_when_oembed_present( $cache, $url, $attr, $post_ID = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
if ( Utils::is_videopress_url( $url ) ) {
|
||||
Jwt_Token_Bridge::enqueue_jwt_token_bridge();
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all VideoPress blocks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_videopress_blocks() {
|
||||
// Register VideoPress Video block.
|
||||
self::register_videopress_video_block();
|
||||
}
|
||||
|
||||
/**
|
||||
* VideoPress video block render method
|
||||
*
|
||||
* @global \WP_Embed $wp_embed WordPress embed handler.
|
||||
*
|
||||
* @param array $block_attributes Block attributes.
|
||||
* @param string $content Current block markup.
|
||||
* @param \WP_Block $block Current block.
|
||||
*
|
||||
* @return string Block markup.
|
||||
*/
|
||||
public static function render_videopress_video_block( $block_attributes, $content, $block ) {
|
||||
global $wp_embed;
|
||||
|
||||
// CSS classes.
|
||||
$align = $block_attributes['align'] ?? null;
|
||||
$align_class = $align ? ' align' . $align : '';
|
||||
$custom_class = isset( $block_attributes['className'] ) ? ' ' . $block_attributes['className'] : '';
|
||||
$classes = 'wp-block-jetpack-videopress jetpack-videopress-player' . $custom_class . $align_class;
|
||||
|
||||
// Inline style.
|
||||
$style = '';
|
||||
$max_width = $block_attributes['maxWidth'] ?? null;
|
||||
|
||||
if ( $max_width && $max_width !== '100%' ) {
|
||||
$style = sprintf( 'max-width: %s;', $max_width );
|
||||
$classes .= ' wp-block-jetpack-videopress--has-max-width';
|
||||
}
|
||||
|
||||
/*
|
||||
* <figcaption /> element
|
||||
* Caption is stored into the block attributes,
|
||||
* but also it was stored into the <figcaption /> element,
|
||||
* meaning that it could be stored in two different places.
|
||||
*/
|
||||
$figcaption = '';
|
||||
|
||||
// Caption from block attributes.
|
||||
$caption = $block_attributes['caption'] ?? null;
|
||||
|
||||
/*
|
||||
* If the caption is not stored into the block attributes,
|
||||
* try to get it from the <figcaption /> element.
|
||||
*/
|
||||
if ( $caption === null ) {
|
||||
preg_match( '/<figcaption>(.*?)<\/figcaption>/', $content, $matches );
|
||||
$caption = $matches[1] ?? null;
|
||||
}
|
||||
|
||||
// If we have a caption, create the <figcaption /> element.
|
||||
if ( $caption !== null ) {
|
||||
$figcaption = sprintf( '<figcaption>%s</figcaption>', wp_kses_post( $caption ) );
|
||||
}
|
||||
|
||||
// Custom anchor from block content.
|
||||
$id_attribute = '';
|
||||
|
||||
// Try to get the custom anchor from the block attributes.
|
||||
if ( isset( $block_attributes['anchor'] ) && $block_attributes['anchor'] ) {
|
||||
$id_attribute = sprintf( 'id="%s"', esc_attr( $block_attributes['anchor'] ) );
|
||||
} elseif ( preg_match( '/<figure[^>]*id="([^"]+)"/', $content, $matches ) ) {
|
||||
// Otherwise, try to get the custom anchor from the <figure /> element.
|
||||
$id_attribute = sprintf( 'id="%s"', esc_attr( $matches[1] ) );
|
||||
}
|
||||
|
||||
// Preview On Hover data.
|
||||
$is_poh_enabled =
|
||||
isset( $block_attributes['posterData']['previewOnHover'] ) &&
|
||||
$block_attributes['posterData']['previewOnHover'];
|
||||
|
||||
$autoplay = $block_attributes['autoplay'] ?? false;
|
||||
$controls = $block_attributes['controls'] ?? false;
|
||||
$poster = $block_attributes['posterData']['url'] ?? null;
|
||||
|
||||
$preview_on_hover = '';
|
||||
|
||||
if ( $is_poh_enabled ) {
|
||||
$preview_on_hover = array(
|
||||
'previewAtTime' => $block_attributes['posterData']['previewAtTime'],
|
||||
'previewLoopDuration' => $block_attributes['posterData']['previewLoopDuration'],
|
||||
'autoplay' => $autoplay,
|
||||
'showControls' => $controls,
|
||||
);
|
||||
|
||||
// Create inline style in case video has a custom poster.
|
||||
$inline_style = '';
|
||||
if ( $poster ) {
|
||||
$inline_style = sprintf(
|
||||
'style="background-image: url(%s); background-size: cover; background-position: center center;"',
|
||||
esc_attr( $poster )
|
||||
);
|
||||
}
|
||||
|
||||
// Expose the preview on hover data to the client.
|
||||
$preview_on_hover = sprintf(
|
||||
'<div class="jetpack-videopress-player__overlay" %s></div><script type="application/json">%s</script>',
|
||||
$inline_style,
|
||||
wp_json_encode( $preview_on_hover, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP )
|
||||
);
|
||||
|
||||
// Set `autoplay` and `muted` attributes to the video element.
|
||||
$block_attributes['autoplay'] = true;
|
||||
$block_attributes['muted'] = true;
|
||||
}
|
||||
|
||||
$figure_template = '
|
||||
<figure class="%1$s" style="%2$s" %3$s>
|
||||
%4$s
|
||||
%5$s
|
||||
%6$s
|
||||
</figure>
|
||||
';
|
||||
|
||||
// VideoPress URL.
|
||||
$guid = $block_attributes['guid'] ?? null;
|
||||
$videopress_url = Utils::get_video_press_url( $guid, $block_attributes );
|
||||
|
||||
$video_wrapper = '';
|
||||
$video_wrapper_classes = 'jetpack-videopress-player__wrapper';
|
||||
|
||||
if ( $videopress_url ) {
|
||||
$videopress_url = wp_kses_post( $videopress_url );
|
||||
|
||||
/*
|
||||
* Provide a fallback iframe for when the oEmbed endpoint fails, e.g.
|
||||
* when the VideoPress backend isn't ready for a freshly uploaded video.
|
||||
* This prevents the published page from showing a bare link.
|
||||
*/
|
||||
$fallback = function ( $output, $url ) use ( $videopress_url ) {
|
||||
if ( $url !== html_entity_decode( $videopress_url, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<iframe title="%1$s" aria-label="%1$s" src="%2$s" width="640" height="360" allowfullscreen data-resize-to-parent="true" allow="clipboard-write"></iframe>',
|
||||
esc_attr__( 'VideoPress Video Player', 'jetpack-videopress-pkg' ),
|
||||
esc_url( preg_replace( '#/v/#', '/embed/', $url, 1 ) )
|
||||
);
|
||||
};
|
||||
|
||||
add_filter( 'embed_maybe_make_link', $fallback, 10, 2 );
|
||||
$oembed_html = apply_filters( 'video_embed_html', $wp_embed->shortcode( array(), $videopress_url ) );
|
||||
remove_filter( 'embed_maybe_make_link', $fallback );
|
||||
|
||||
$video_wrapper = sprintf(
|
||||
'<div class="%s">%s %s</div>',
|
||||
$video_wrapper_classes,
|
||||
$preview_on_hover,
|
||||
$oembed_html
|
||||
);
|
||||
|
||||
/*
|
||||
* Self-heal failed oEmbed cache for VideoPress URLs.
|
||||
*
|
||||
* When the VideoPress backend isn't ready for a freshly uploaded video,
|
||||
* WordPress caches '{{unknown}}' in post meta with a TTL that is too long
|
||||
* for this use case. Clear recent failures so the next page render retries
|
||||
* oEmbed discovery, keeping the fallback iframe above temporary.
|
||||
*/
|
||||
$post_id = $block->context['postId'] ?? get_the_ID();
|
||||
|
||||
if ( $post_id ) {
|
||||
$key_suffix = md5( $videopress_url . serialize( wp_embed_defaults( $videopress_url ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Matching WP_Embed cache key format.
|
||||
$oembed_value = get_post_meta( $post_id, '_oembed_' . $key_suffix, true );
|
||||
$oembed_time = (int) get_post_meta( $post_id, '_oembed_time_' . $key_suffix, true );
|
||||
|
||||
/*
|
||||
* Only clear the '{{unknown}}' cache entry when it is recent, to avoid
|
||||
* disabling WordPress's oEmbed backoff for persistent provider failures.
|
||||
*/
|
||||
if (
|
||||
'{{unknown}}' === $oembed_value
|
||||
&& ( ! $oembed_time || ( time() - $oembed_time ) < MINUTE_IN_SECONDS )
|
||||
) {
|
||||
delete_post_meta( $post_id, '_oembed_' . $key_suffix );
|
||||
delete_post_meta( $post_id, '_oembed_time_' . $key_suffix );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get premium content from block context.
|
||||
$premium_block_plan_id = isset( $block->context['premium-content/planId'] ) ? intval( $block->context['premium-content/planId'] ) : 0;
|
||||
$is_premium_content_child = isset( $block->context['isPremiumContentChild'] ) ? (bool) $block->context['isPremiumContentChild'] : false;
|
||||
$maybe_premium_script = '';
|
||||
if ( $is_premium_content_child ) {
|
||||
Access_Control::instance()->set_guid_subscription( $guid, $premium_block_plan_id );
|
||||
$escaped_guid = wp_json_encode( $guid, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
|
||||
$script_content = "if ( ! window.__guidsToPlanIds ) { window.__guidsToPlanIds = {}; }; window.__guidsToPlanIds[$escaped_guid] = $premium_block_plan_id;";
|
||||
$maybe_premium_script = '<script>' . $script_content . '</script>';
|
||||
}
|
||||
|
||||
// $id_attribute, $video_wrapper, $figcaption properly escaped earlier in the code.
|
||||
return sprintf(
|
||||
$figure_template,
|
||||
esc_attr( $classes ),
|
||||
esc_attr( $style ),
|
||||
$id_attribute,
|
||||
$video_wrapper,
|
||||
$figcaption,
|
||||
$maybe_premium_script
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the VideoPress block editor block,
|
||||
* AKA "VideoPress Block v6".
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_videopress_video_block() {
|
||||
/*
|
||||
* If only Jetpack is active, and if the VideoPress module is not active,
|
||||
* we can register the block just to display a placeholder to turn on the module.
|
||||
* That invitation is only useful for admins though.
|
||||
*/
|
||||
if (
|
||||
Status::is_jetpack_plugin_without_videopress_module_active()
|
||||
&& ! Status::is_standalone_plugin_active()
|
||||
&& ! current_user_can( 'jetpack_activate_modules' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$videopress_video_metadata_file = __DIR__ . '/../build/block-editor/blocks/video/block.json';
|
||||
$videopress_video_metadata_file_exists = file_exists( $videopress_video_metadata_file );
|
||||
if ( ! $videopress_video_metadata_file_exists ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$videopress_video_metadata = json_decode(
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
file_get_contents( $videopress_video_metadata_file )
|
||||
);
|
||||
|
||||
// Pick the block name straight from the block metadata .json file.
|
||||
$videopress_video_block_name = $videopress_video_metadata->name;
|
||||
|
||||
// Is the block already registered?
|
||||
$is_block_registered = \WP_Block_Type_Registry::get_instance()->is_registered( $videopress_video_block_name );
|
||||
|
||||
// Do not register if the block is already registered.
|
||||
if ( $is_block_registered ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$registration = register_block_type(
|
||||
$videopress_video_metadata_file,
|
||||
array(
|
||||
'render_callback' => array( __CLASS__, 'render_videopress_video_block' ),
|
||||
'render_email_callback' => array( Video_Block_Email_Renderer::class, 'render' ),
|
||||
'uses_context' => array( 'premium-content/planId', 'isPremiumContentChild', 'selectedPlanId' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Do not enqueue scripts if the block could not be registered.
|
||||
if ( empty( $registration ) || empty( $registration->editor_script_handles ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extensions use Connection_Initial_State::render_script with script handle as parameter.
|
||||
if ( is_array( $registration->editor_script_handles ) ) {
|
||||
$script_handle = $registration->editor_script_handles[0];
|
||||
} else {
|
||||
$script_handle = $registration->editor_script_handles;
|
||||
}
|
||||
|
||||
// Register and enqueue scripts used by the VideoPress video block.
|
||||
Block_Editor_Extensions::init( $script_handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the VideoPress Iframe API script
|
||||
* when the URL of oEmbed HTML is a VideoPress URL.
|
||||
*
|
||||
* @param string|false $cache The cached HTML result, stored in post meta.
|
||||
* @param string $url The attempted embed URL.
|
||||
* @param array $attr An array of shortcode attributes.
|
||||
* @param int $post_ID Post ID.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public static function enqueue_videopress_iframe_api_script( $cache, $url, $attr, $post_ID ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
if ( Utils::is_videopress_url( $url ) ) {
|
||||
// Enqueue the VideoPress IFrame API in the front-end.
|
||||
wp_enqueue_script(
|
||||
self::JETPACK_VIDEOPRESS_IFRAME_API_HANDLER,
|
||||
'https://s0.wp.com/wp-content/plugins/video/assets/js/videojs/videopress-iframe-api.js',
|
||||
array(),
|
||||
gmdate( 'YW' ),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Jwt_Token_Bridge
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* VideoPress Jwt_Token_Bridge class.
|
||||
*/
|
||||
class Jwt_Token_Bridge {
|
||||
|
||||
/**
|
||||
* The handle used to enqueue the script
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SCRIPT_HANDLE = 'media-video-jwt-bridge';
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*
|
||||
* This method should be called only once by the Initializer class. Do not call this method again.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( ! Status::is_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Expose the VideoPress token to the Block Editor context.
|
||||
add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_jwt_token_bridge' ), 1 );
|
||||
|
||||
// Expose the VideoPress token to the WPAdmin context.
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_jwt_token_bridge' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bridge script URL depending on the environment we are in
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_bridge_url() {
|
||||
return plugins_url( '../build/lib/token-bridge.js', __FILE__ );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the jwt bridge script.
|
||||
*/
|
||||
public static function enqueue_jwt_token_bridge() {
|
||||
global $post;
|
||||
$post_id = isset( $post->ID ) ? absint( $post->ID ) : 0;
|
||||
|
||||
$bridge_url = self::get_bridge_url();
|
||||
|
||||
self::enqueue_script();
|
||||
|
||||
wp_localize_script(
|
||||
self::SCRIPT_HANDLE,
|
||||
'videopressAjax',
|
||||
array(
|
||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||
'bridgeUrl' => $bridge_url,
|
||||
'post_id' => $post_id,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues only the JS script
|
||||
*
|
||||
* @param string $handle The script handle to identify the script.
|
||||
* @return bool True if the script was successfully localized, false otherwise.
|
||||
*/
|
||||
public static function enqueue_script( $handle = self::SCRIPT_HANDLE ) {
|
||||
return wp_enqueue_script(
|
||||
$handle,
|
||||
self::get_bridge_url(),
|
||||
array(),
|
||||
Package_Version::PACKAGE_VERSION,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack VideoPress: Module_Control class
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* To handle VideoPress module statuses
|
||||
*/
|
||||
class Module_Control {
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*
|
||||
* This method should onlybe called once by the Initializer class. Do not call this method again.
|
||||
*/
|
||||
public static function init() {
|
||||
add_filter( 'jetpack_get_available_standalone_modules', array( __CLASS__, 'add_videopress_to_array' ), 10, 1 );
|
||||
if ( Status::is_standalone_plugin_active() ) {
|
||||
// If the stand-alone plugin is active, videopress module will always be considered active
|
||||
add_filter( 'jetpack_active_modules', array( __CLASS__, 'add_videopress_to_array' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds videopress to the list of available/active modules
|
||||
*
|
||||
* @param array $modules Array with modules slugs.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_videopress_to_array( $modules ) {
|
||||
return array_merge( array( 'videopress' ), $modules );
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Options
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Current_Plan;
|
||||
use Jetpack_Options;
|
||||
|
||||
/**
|
||||
* VideoPress Options class.
|
||||
*/
|
||||
class Options {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string $option_name The 'videopress' option name
|
||||
*/
|
||||
public static $option_name = 'videopress';
|
||||
|
||||
/**
|
||||
* VideoPress Options.
|
||||
*
|
||||
* @var array $options An array of associated VideoPress options (default empty)
|
||||
*/
|
||||
protected static $options = array();
|
||||
|
||||
/**
|
||||
* Get VideoPress options
|
||||
*
|
||||
* @return array An array of VideoPress options.
|
||||
*/
|
||||
public static function get_options() {
|
||||
// Make sure we only get options from the database and services once per connection.
|
||||
if ( array() !== self::$options ) {
|
||||
return self::$options;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'meta' => array(
|
||||
'max_upload_size' => 0,
|
||||
),
|
||||
);
|
||||
|
||||
self::$options = Jetpack_Options::get_option( self::$option_name, array() );
|
||||
self::$options = array_merge( $defaults, self::$options );
|
||||
|
||||
// Make sure that the shadow blog id never comes from the options, but instead uses the
|
||||
// associated shadow blog id, if videopress is enabled.
|
||||
self::$options['shadow_blog_id'] = 0;
|
||||
|
||||
// Use the Jetpack ID for the shadow blog ID if we have a plan that supports VideoPress.
|
||||
if ( Current_Plan::supports( 'videopress' ) ) {
|
||||
self::$options['shadow_blog_id'] = Jetpack_Options::get_option( 'id' );
|
||||
}
|
||||
|
||||
return self::$options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update VideoPress options
|
||||
*
|
||||
* @param mixed $options VideoPress options.
|
||||
*/
|
||||
public static function update_options( $options ) {
|
||||
Jetpack_Options::update_option( self::$option_name, $options );
|
||||
|
||||
self::$options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the VideoPress module is deactivated.
|
||||
*/
|
||||
public static function delete_options() {
|
||||
Jetpack_Options::delete_option( self::$option_name );
|
||||
|
||||
self::$options = array();
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*/
|
||||
class Package_Version {
|
||||
const PACKAGE_VERSION = '0.38.2';
|
||||
|
||||
const PACKAGE_SLUG = 'videopress';
|
||||
|
||||
/**
|
||||
* Adds the package slug and version to the package version tracker's data.
|
||||
*
|
||||
* @param array $package_versions The package version array.
|
||||
*
|
||||
* @return array The packge version array.
|
||||
*/
|
||||
public static function send_package_version_to_tracker( $package_versions ) {
|
||||
$package_versions[ self::PACKAGE_SLUG ] = self::PACKAGE_VERSION;
|
||||
return $package_versions;
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* The Plan class.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* The Plan class.
|
||||
*/
|
||||
class Plan {
|
||||
/**
|
||||
* The meta name used to store the cache date
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CACHE_DATE_META_NAME = 'videopress-cache-date';
|
||||
|
||||
/**
|
||||
* Valid pediord for the cache: One week.
|
||||
*/
|
||||
const CACHE_VALIDITY_PERIOD = 7 * DAY_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* The meta name used to store the cache
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CACHE_META_NAME = 'videopress-cache';
|
||||
|
||||
/**
|
||||
* Checks if the cache is old, meaning we need to fetch new data from WPCOM
|
||||
*/
|
||||
private static function is_cache_old() {
|
||||
if ( empty( self::get_product_from_cache() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$cache_date = get_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, true );
|
||||
return time() - (int) $cache_date > ( self::CACHE_VALIDITY_PERIOD );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the product list from the user cache
|
||||
*/
|
||||
private static function get_product_from_cache() {
|
||||
return get_user_meta( get_current_user_id(), self::CACHE_META_NAME, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the product data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_product() {
|
||||
$request_url = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
|
||||
$wpcom_request = wp_remote_get( esc_url_raw( $request_url ) );
|
||||
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
|
||||
|
||||
if ( 200 === $response_code ) {
|
||||
$products = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
|
||||
if ( ! isset( $products->jetpack_videopress ) || ! isset( $products->jetpack_videopress_monthly ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Pick the desired product...
|
||||
$product = $products->jetpack_videopress;
|
||||
|
||||
// ... and store it into the cache.
|
||||
update_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, time() );
|
||||
update_user_meta( get_current_user_id(), self::CACHE_META_NAME, $product );
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
return new \WP_Error(
|
||||
'failed_to_fetch_data',
|
||||
esc_html__( 'Unable to fetch the requested data.', 'jetpack-videopress-pkg' ),
|
||||
array(
|
||||
'status' => $response_code,
|
||||
'request' => $wpcom_request,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the pricing array with the discount information.
|
||||
*
|
||||
* @param object $product - The product object.
|
||||
* @return int|false Discount percentage.
|
||||
*/
|
||||
public static function get_coupon_discount( $product ) {
|
||||
// Check whether the product has a coupon.
|
||||
if ( ! isset( $product->sale_coupon ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$product_id = $product->product_id;
|
||||
$coupon = $product->sale_coupon;
|
||||
|
||||
// Check product is covered by the coupon.
|
||||
if ( ! in_array( $product_id, $coupon->product_ids, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether it is still valid.
|
||||
$coupon_start_date = strtotime( $coupon->start_date );
|
||||
$coupon_expires = strtotime( $coupon->expires );
|
||||
if ( $coupon_start_date > time() || $coupon_expires < time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $coupon->discount ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return intval( $coupon->discount );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return details about the VideoPress product price
|
||||
*
|
||||
* @return array Produce price details
|
||||
*/
|
||||
public static function get_product_price() {
|
||||
$request_url = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
|
||||
$wpcom_request = wp_remote_get( esc_url_raw( $request_url ) );
|
||||
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
|
||||
|
||||
if ( 200 === $response_code ) {
|
||||
$products = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
|
||||
|
||||
$products_list = array();
|
||||
|
||||
if ( isset( $products->jetpack_videopress ) ) {
|
||||
$videopress_yearly = $products->jetpack_videopress;
|
||||
// get_coupon_discount
|
||||
$products_list['yearly'] = array(
|
||||
'name' => $videopress_yearly->product_name,
|
||||
'slug' => $videopress_yearly->product_slug,
|
||||
'price' => $videopress_yearly->cost,
|
||||
'priceByMonth' => round( $videopress_yearly->cost / 12, 2 ),
|
||||
'currency' => $videopress_yearly->currency_code,
|
||||
);
|
||||
|
||||
$discount = self::get_coupon_discount( $videopress_yearly );
|
||||
|
||||
if ( $discount ) {
|
||||
$products_list['yearly']['discount'] = $discount;
|
||||
$products_list['yearly']['salePrice'] = round( $videopress_yearly->cost * ( 1 - $discount / 100 ), 2 );
|
||||
$products_list['yearly']['salePriceByMonth'] = round( ( $videopress_yearly->cost * ( 1 - $discount / 100 ) / 12 ), 2 );
|
||||
} else {
|
||||
$products_list['yearly']['salePrice'] = $videopress_yearly->cost;
|
||||
$products_list['yearly']['salePriceByMonth'] = round( $videopress_yearly->cost / 12, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $products->jetpack_videopress_monthly ) ) {
|
||||
$videopress_monthly = $products->jetpack_videopress_monthly;
|
||||
|
||||
$products_list['monthly'] = array(
|
||||
'name' => $videopress_monthly->product_name,
|
||||
'slug' => $videopress_monthly->product_slug,
|
||||
'price' => $videopress_monthly->cost,
|
||||
'currency' => $videopress_monthly->currency_code,
|
||||
);
|
||||
}
|
||||
|
||||
return $products_list;
|
||||
}
|
||||
|
||||
return new \WP_Error(
|
||||
'failed_to_fetch_data',
|
||||
esc_html__( 'Unable to fetch the requested data.', 'jetpack-videopress-pkg' ),
|
||||
array(
|
||||
'status' => $response_code,
|
||||
'request' => $wpcom_request,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
/**
|
||||
* The VideoPress REST Controller.
|
||||
*
|
||||
* Registers the `/jetpack/v4/videopress/*` routes backing the
|
||||
* modernized wp-build dashboard. Currently exposes one route — a
|
||||
* user-signed proxy to the WPCOM `sites/{id}/stats/video-plays`
|
||||
* endpoint — needed by the Overview screen's KPI / trends / top-N
|
||||
* cards.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* REST routes for the modernized VideoPress admin UI.
|
||||
*/
|
||||
class Rest_Controller {
|
||||
|
||||
/**
|
||||
* REST namespace used by this package's modernization routes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REST_NAMESPACE = 'jetpack/v4/videopress';
|
||||
|
||||
/**
|
||||
* Hook the route registration on `rest_api_init`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the VideoPress REST routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_routes() {
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
'/stats/video-plays',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( __CLASS__, 'get_stats_video_plays' ),
|
||||
'permission_callback' => array( __CLASS__, 'permissions_callback' ),
|
||||
'args' => self::stats_video_plays_args(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query params accepted by the video-plays proxy. Forwarded verbatim
|
||||
* to WPCOM after permission and shape validation; `complete_stats` is
|
||||
* always forced to `true` (the only mode the Overview cares about)
|
||||
* and is therefore not exposed as an incoming param.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function stats_video_plays_args() {
|
||||
return array(
|
||||
'period' => array(
|
||||
'description' => __( 'Period unit: day, week, month, or year.', 'jetpack-videopress-pkg' ),
|
||||
'type' => 'string',
|
||||
'enum' => array( 'day', 'week', 'month', 'year' ),
|
||||
),
|
||||
'num' => array(
|
||||
'description' => __( 'Number of periods to include.', 'jetpack-videopress-pkg' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
'maximum' => 365,
|
||||
),
|
||||
'date' => array(
|
||||
'description' => __( 'Most recent day to include in results (YYYY-MM-DD).', 'jetpack-videopress-pkg' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date',
|
||||
),
|
||||
'start_date' => array(
|
||||
'description' => __( 'Starting date for range queries (YYYY-MM-DD).', 'jetpack-videopress-pkg' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission callback. Admin-gated. The upstream call is blog-signed,
|
||||
* matching the existing `Stats::fetch_video_plays` path; no user-level
|
||||
* WPCOM connection is required.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy the video-plays stats endpoint.
|
||||
*
|
||||
* Forwards the whitelisted query params to WPCOM (REST v1.1, blog-signed
|
||||
* — matching the existing `Stats::fetch_video_plays` path) and forces
|
||||
* `complete_stats=true`. In complete-stats mode, each day entry carries
|
||||
* `total.views`, `total.impressions`, and `total.watch_time` (in hours)
|
||||
* plus a per-video `data[]` array whose entries have `post_id`, `title`,
|
||||
* `views`, `impressions`, `watch_time` (hours), and `retention_rate`.
|
||||
* The `plays` field is NOT returned in complete-stats mode.
|
||||
*
|
||||
* @param WP_REST_Request $request Incoming request.
|
||||
* @return mixed Decoded JSON response from WPCOM, or WP_Error on failure.
|
||||
*/
|
||||
public static function get_stats_video_plays( WP_REST_Request $request ) {
|
||||
$blog_id = (int) Jetpack_Options::get_option( 'id' );
|
||||
if ( ! $blog_id ) {
|
||||
return new WP_Error(
|
||||
'videopress_stats_not_connected',
|
||||
esc_html__( 'This site is not connected to WordPress.com.', 'jetpack-videopress-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$params = array( 'complete_stats' => 'true' );
|
||||
foreach ( array_keys( self::stats_video_plays_args() ) as $key ) {
|
||||
$value = $request->get_param( $key );
|
||||
if ( $value !== null && $value !== '' ) {
|
||||
$params[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$path = sprintf(
|
||||
'sites/%d/stats/video-plays?%s',
|
||||
$blog_id,
|
||||
http_build_query( $params )
|
||||
);
|
||||
$response = Client::wpcom_json_api_request_as_blog( $path );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error(
|
||||
'videopress_stats_request_failed',
|
||||
$response->get_error_message(),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
$status = (int) wp_remote_retrieve_response_code( $response );
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( 200 !== $status ) {
|
||||
$message = is_array( $body ) && isset( $body['message'] )
|
||||
? (string) $body['message']
|
||||
: esc_html__( 'Unable to fetch VideoPress stats.', 'jetpack-videopress-pkg' );
|
||||
return new WP_Error(
|
||||
'videopress_stats_request_failed',
|
||||
$message,
|
||||
array( 'status' => $status ? $status : 500 )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response( $body );
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Provides site data sourced from WPCOM
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Provides site data sourced from WPCOM
|
||||
*/
|
||||
class Site {
|
||||
|
||||
/**
|
||||
* Returns all the data provided by WPCOM for the site.
|
||||
*
|
||||
* @return int|WP_Error the total of plays for today, or WP_Error on failure.
|
||||
*/
|
||||
public static function get_site_info() {
|
||||
$error = new WP_Error(
|
||||
'videopress_site_error',
|
||||
__( 'Could not fetch site information from the service', 'jetpack-videopress-pkg' )
|
||||
);
|
||||
|
||||
$request_path = sprintf( 'sites/%d?force=wpcom', Data::get_blog_id() );
|
||||
$response = Client::wpcom_json_api_request_as_blog( $request_path, '1.1', array(), null, 'rest' );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 !== $response_code ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
||||
return json_decode( $body, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the purchases provided by WPCOM for the site.
|
||||
*
|
||||
* @return array the list of purchases, or an empty list on failure.
|
||||
*/
|
||||
public static function get_purchases() {
|
||||
$request_path = sprintf( 'sites/%1$d/purchases?locale=%2$s', Data::get_blog_id(), get_user_locale() );
|
||||
$response = Client::wpcom_json_api_request_as_blog( $request_path, '1.1', array(), null, 'rest' );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 !== $response_code ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
||||
return json_decode( $body, true );
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* Provides data stats about videos inside VideoPress
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Provides data stats about videos inside VideoPress
|
||||
*/
|
||||
class Stats {
|
||||
/**
|
||||
* Hit WPCOM video-plays stats endpoint.
|
||||
*
|
||||
* @param array $args Request args.
|
||||
* @return array|WP_Error WP HTTP response on success
|
||||
*/
|
||||
public static function fetch_video_plays( $args = array() ) {
|
||||
$blog_id = VideoPressToken::blog_id();
|
||||
|
||||
$endpoint = sprintf(
|
||||
'sites/%d/stats/video-plays?check_stats_module=false',
|
||||
$blog_id
|
||||
);
|
||||
|
||||
if ( is_array( $args ) && ! empty( $args ) ) {
|
||||
$endpoint .= '&' . http_build_query( $args );
|
||||
}
|
||||
|
||||
$result = Client::wpcom_json_api_request_as_blog( $endpoint );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response = $result['http_response'];
|
||||
$response_code = $response->get_status();
|
||||
$response_body = json_decode( $response->get_data(), true );
|
||||
|
||||
if ( 200 !== $response_code ) {
|
||||
return new WP_Error(
|
||||
'videopress_stats_error',
|
||||
$response_body
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'code' => $response_code,
|
||||
'data' => $response_body,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the counter of today's plays for all videos.
|
||||
*
|
||||
* @return int|WP_Error the total of plays for today, or WP_Error on failure.
|
||||
*/
|
||||
public static function get_today_plays() {
|
||||
$response = self::fetch_video_plays();
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$data = $response['data'];
|
||||
|
||||
if ( ! $data || ! isset( $data['days'] ) || ! is_countable( $data['days'] ) || count( $data['days'] ) === 0 ) {
|
||||
return new WP_Error(
|
||||
'videopress_stats_error',
|
||||
__( 'Could not find any stats from the service', 'jetpack-videopress-pkg' )
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* The only result here is today's stats
|
||||
*/
|
||||
$today_stats = array_pop( $data['days'] );
|
||||
|
||||
return $today_stats['total_plays'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the featured stats for VideoPress.
|
||||
*
|
||||
* @param int $period_count (optional) The number of days to consider.
|
||||
* @param string $period (optional) The period to consider.
|
||||
*
|
||||
* @return array|WP_Error a list of stats, or WP_Error on failure.
|
||||
*/
|
||||
public static function get_featured_stats( $period_count = 14, $period = 'day' ) {
|
||||
$response = self::fetch_video_plays(
|
||||
array(
|
||||
'period' => $period,
|
||||
'num' => $period_count,
|
||||
'complete_stats' => true,
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$data = $response['data'];
|
||||
|
||||
if ( ! $data ) {
|
||||
return new WP_Error(
|
||||
'videopress_stats_error',
|
||||
__( 'Could not find any stats from the service', 'jetpack-videopress-pkg' )
|
||||
);
|
||||
}
|
||||
|
||||
// Get only the list of dates
|
||||
$dates = $data['days'];
|
||||
|
||||
// Organize the data into the planned stats
|
||||
return self::prepare_featured_stats( $dates, $period_count, $period );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the featured stats for VideoPress.
|
||||
*
|
||||
* @param array $dates The list of dates returned by the API.
|
||||
* @param int $period_count The total number of days to consider.
|
||||
* @param string $period The period to consider.
|
||||
* @return array a list of stats.
|
||||
*/
|
||||
public static function prepare_featured_stats( $dates, $period_count, $period = 'day' ) {
|
||||
/**
|
||||
* Ensure the sorting of the dates, recent ones first.
|
||||
* This way, the first 7 positions are from the last 7 days,
|
||||
* and the next 7 positions are from the 7 days before it.
|
||||
*/
|
||||
krsort( $dates );
|
||||
$period_of_data = floor( $period_count / 2 );
|
||||
$period = $period === 'day' ? __( 'day', 'jetpack-videopress-pkg' ) : __( 'year', 'jetpack-videopress-pkg' );
|
||||
|
||||
// template for the response
|
||||
$featured_stats = array(
|
||||
// translators: %1$d is the number of units of time, %2$s is the period in which the units of time are measured ex. 'day' or 'year'.
|
||||
'label' => sprintf( _n( 'last %1$d %2$s', 'last %1$d %2$ss', (int) $period_of_data, 'jetpack-videopress-pkg' ), $period_of_data, $period ),
|
||||
'period' => $period,
|
||||
'data' => array(
|
||||
'views' => array(
|
||||
'current' => 0,
|
||||
'previous' => 0,
|
||||
),
|
||||
'impressions' => array(
|
||||
'current' => 0,
|
||||
'previous' => 0,
|
||||
),
|
||||
'watch_time' => array(
|
||||
'current' => 0,
|
||||
'previous' => 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Go through the dates to compute the stats
|
||||
$counter = 0;
|
||||
foreach ( $dates as $date_info ) {
|
||||
$date_totals = $date_info['total'];
|
||||
|
||||
if ( $counter < floor( $period_count / 2 ) ) {
|
||||
|
||||
// the first 7 elements are for the current period
|
||||
$featured_stats['data']['views']['current'] += $date_totals['views'];
|
||||
$featured_stats['data']['impressions']['current'] += $date_totals['impressions'];
|
||||
$featured_stats['data']['watch_time']['current'] += $date_totals['watch_time'];
|
||||
|
||||
} else {
|
||||
|
||||
// the next 7 elements are for the previous period
|
||||
$featured_stats['data']['views']['previous'] += $date_totals['views'];
|
||||
$featured_stats['data']['impressions']['previous'] += $date_totals['impressions'];
|
||||
$featured_stats['data']['watch_time']['previous'] += $date_totals['watch_time'];
|
||||
|
||||
}
|
||||
|
||||
++$counter;
|
||||
}
|
||||
|
||||
return $featured_stats;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* The class that provides information about VideoPress Status
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\Modules;
|
||||
|
||||
/**
|
||||
* The class that provides information about VideoPress Status
|
||||
*/
|
||||
class Status {
|
||||
|
||||
/**
|
||||
* Returns whether VideoPress is active
|
||||
* either as a Jetpack module or as a stand alone plugin
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_active() {
|
||||
return self::is_jetpack_plugin_and_videopress_module_active() || self::is_standalone_plugin_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Jetpack plugin is active
|
||||
*/
|
||||
public static function is_jetpack_plugin_active() {
|
||||
return class_exists( 'Jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Jetpack plugin
|
||||
* and its VideoPress module are active.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_jetpack_plugin_and_videopress_module_active() {
|
||||
return class_exists( 'Jetpack' ) && ( new Modules() )->is_active( 'videopress' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Jetpack plugin is active
|
||||
* but the VideoPress module is not active.
|
||||
*
|
||||
* @since 0.28.2
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_jetpack_plugin_without_videopress_module_active() {
|
||||
return class_exists( 'Jetpack' ) && ! ( new Modules() )->is_active( 'videopress' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the VideoPress stand alone plugin is active
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_standalone_plugin_active() {
|
||||
return class_exists( 'Jetpack_VideoPress_Plugin' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the registrant plugin is active
|
||||
* either as a Jetpack module (via Jetpack plugin)
|
||||
* or as a stand-alone plugin.
|
||||
*
|
||||
* @return boolean True if the register plugin is active.
|
||||
*/
|
||||
public static function is_registrant_plugin_active() {
|
||||
return self::is_jetpack_plugin_active() || self::is_standalone_plugin_active();
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Uploader Exception
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* VideoPress Uploader Exception class
|
||||
*/
|
||||
class Upload_Exception extends \Exception {
|
||||
const ERROR_INVALID_ATTACHMENT_ID = 0;
|
||||
const ERROR_FILE_NOT_FOUND = 1;
|
||||
const ERROR_MIME_TYPE_NOT_SUPPORTED = 2;
|
||||
const ERROR_MALFORMED_FILE = 3;
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Uploader
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* VideoPress Uploader class
|
||||
*
|
||||
* Handles the upload from the Media Library to VideoPress servers
|
||||
*/
|
||||
class Uploader_Rest_Endpoints {
|
||||
|
||||
/**
|
||||
* Initializes the endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'rest_api_init', array( __CLASS__, 'register_rest_endpoints' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
$id_arg = array(
|
||||
'description' => __( 'The ID of the attachment you want to upload to VideoPress', 'jetpack-videopress-pkg' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'validate_callback' => __CLASS__ . '::validate_attachment_id',
|
||||
);
|
||||
register_rest_route(
|
||||
'videopress/v1',
|
||||
'upload/(?P<attachment_id>\d+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::check_status',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
'args' => array(
|
||||
'attachment_id' => $id_arg,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => __CLASS__ . '::do_upload',
|
||||
'permission_callback' => __CLASS__ . '::permissions_callback',
|
||||
'args' => array(
|
||||
'attachment_id' => $id_arg,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the user have permission to perform the upload
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'upload_files' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the attachment ID argument
|
||||
*
|
||||
* @param integer|string $value The attachment ID passed as an argument to the endpoint.
|
||||
* @return boolean|WP_Error
|
||||
*/
|
||||
public static function validate_attachment_id( $value ) {
|
||||
return Uploader::is_valid_attachment_id( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint callback for the GET method. Checks the upload status
|
||||
*
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function check_status( $request ) {
|
||||
$attachment_id = $request->get_param( 'attachment_id' );
|
||||
try {
|
||||
$uploader = new Uploader( $attachment_id );
|
||||
$status = $uploader->check_status();
|
||||
return rest_ensure_response( $status );
|
||||
} catch ( Upload_Exception $e ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
$e->getMessage(),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint callback for the POST method. Uploads the video
|
||||
*
|
||||
* @param \WP_REST_Request $request The request object.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function do_upload( $request ) {
|
||||
$attachment_id = $request->get_param( 'attachment_id' );
|
||||
try {
|
||||
$uploader = new Uploader( $attachment_id );
|
||||
$status = $uploader->upload();
|
||||
return rest_ensure_response( $status );
|
||||
} catch ( Upload_Exception $e ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
$e->getMessage(),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Uploader
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Jetpack_Options;
|
||||
use VideoPressUploader\File_Exception;
|
||||
use VideoPressUploader\Tus_Client;
|
||||
|
||||
/**
|
||||
* VideoPress Uploader class
|
||||
*
|
||||
* Handles the upload from the Media Library to VideoPress servers
|
||||
*/
|
||||
class Uploader {
|
||||
|
||||
/**
|
||||
* The key of the post meta that holds the ID of the attachment that holds the VideoPress video, in case this attachment was uploaded before.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const UPLOADED_KEY = '_videopress_uploaded_id';
|
||||
|
||||
/**
|
||||
* The chunk size of each upload step
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const CHUNK_SIZE = 5000000;
|
||||
|
||||
/**
|
||||
* The Tus Client instance
|
||||
*
|
||||
* @var Tus_Client
|
||||
*/
|
||||
protected $client = null;
|
||||
|
||||
/**
|
||||
* The attachment ID
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $attachment_id;
|
||||
|
||||
/**
|
||||
* Checks whether this feature is supported by the server
|
||||
*
|
||||
* @param int $attachment_id The ID of the video attachment we want to upload to VideoPress.
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_valid_attachment_id( $attachment_id ) {
|
||||
$file_path = get_attached_file( $attachment_id );
|
||||
if ( ! $file_path || ! is_readable( $file_path ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! str_starts_with( get_post_mime_type( $attachment_id ), 'video/' ) ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the object
|
||||
*
|
||||
* @throws Upload_Exception If attachment is invalid or server does not support it.
|
||||
* @param int $attachment_id The ID of the video attachment we want to upload to VideoPress.
|
||||
*/
|
||||
public function __construct( $attachment_id ) {
|
||||
$this->attachment_id = $attachment_id;
|
||||
if ( ! $this->get_file_path() ) {
|
||||
throw new Upload_Exception( __( 'Invalid attachment ID', 'jetpack-videopress-pkg' ), Upload_Exception::ERROR_INVALID_ATTACHMENT_ID );
|
||||
}
|
||||
if ( ! is_readable( $this->get_file_path() ) ) {
|
||||
throw new Upload_Exception( __( 'File not found', 'jetpack-videopress-pkg' ), Upload_Exception::ERROR_FILE_NOT_FOUND );
|
||||
}
|
||||
if ( ! $this->file_has_supported_mime_type() ) {
|
||||
throw new Upload_Exception( __( 'Mime type not supported', 'jetpack-videopress-pkg' ), Upload_Exception::ERROR_MIME_TYPE_NOT_SUPPORTED );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the video file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_path() {
|
||||
return get_attached_file( $this->attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mime type of the attachment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_mime_type() {
|
||||
return get_post_mime_type( $this->attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the video file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_name() {
|
||||
return basename( $this->get_file_path() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the video file
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_file_size() {
|
||||
return filesize( $this->get_file_path() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the mime type of the attachment is supported to be uploaded
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function file_has_supported_mime_type() {
|
||||
return str_starts_with( $this->get_file_mime_type(), 'video/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the VideoPress upload token
|
||||
*
|
||||
* @throws Upload_Exception If it fails to fetch the token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_upload_token() {
|
||||
return VideoPressToken::videopress_upload_jwt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique upload key for this attachment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_key() {
|
||||
return sprintf( 's-%d-v-%d', Jetpack_Options::get_option( 'id' ), $this->attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current attachment as uploaded and stores the ID of the VideoPress video attachment ID
|
||||
*
|
||||
* @param int $new_attachment_id The ID of the new attachment created to hold the VideoPress video.
|
||||
* @return void
|
||||
*/
|
||||
protected function mark_as_uploaded( $new_attachment_id ) {
|
||||
update_post_meta( $this->attachment_id, self::UPLOADED_KEY, $new_attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current attachment as not being uploaded before. Deletes the reference to the videopress attachment
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function unmark_as_uploaded() {
|
||||
delete_post_meta( $this->attachment_id, self::UPLOADED_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this attachment was uploaded before
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_uploaded() {
|
||||
return ! empty( $this->get_uploaded_attachment_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the VideoPress video attachment in case this attachment was uploaded before
|
||||
*
|
||||
* @return boolean|string False if value is absent. Post ID on success.
|
||||
*/
|
||||
public function get_uploaded_attachment_id() {
|
||||
return get_post_meta( $this->attachment_id, self::UPLOADED_KEY, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the instance of the Tus_Client
|
||||
*
|
||||
* @return Tus_Client
|
||||
*/
|
||||
public function get_client() {
|
||||
if ( $this->client !== null ) {
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
$this->client = new Tus_Client( $this->get_key(), $this->get_upload_token(), Jetpack_Options::get_option( 'id' ) );
|
||||
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a chunk of the file
|
||||
*
|
||||
* @return array With the status of the upload
|
||||
*/
|
||||
public function upload() {
|
||||
if ( $this->is_uploaded() ) {
|
||||
return $this->check_status();
|
||||
}
|
||||
try {
|
||||
$this->get_client()->file( $this->get_file_path(), $this->get_file_name() );
|
||||
|
||||
$bytes_uploaded = $this->get_client()->upload( self::CHUNK_SIZE );
|
||||
|
||||
if ( $bytes_uploaded === $this->get_file_size() ) {
|
||||
$this->mark_as_uploaded( $this->get_client()->get_uploaded_video_details()['media_id'] );
|
||||
return array(
|
||||
'status' => 'complete',
|
||||
'bytes_uploaded' => $bytes_uploaded,
|
||||
'file_size' => $this->get_file_size(),
|
||||
'file_name' => $this->get_file_name(),
|
||||
'upload_key' => $this->get_key(),
|
||||
'uploaded_details' => $this->get_client()->get_uploaded_video_details(),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'status' => 'uploading',
|
||||
'bytes_uploaded' => $bytes_uploaded,
|
||||
'file_size' => $this->get_file_size(),
|
||||
'file_name' => $this->get_file_name(),
|
||||
'upload_key' => $this->get_key(),
|
||||
);
|
||||
} catch ( \Exception $e ) {
|
||||
return array(
|
||||
'status' => 'error',
|
||||
'bytes_uploaded' => -1,
|
||||
'file_size' => $this->get_file_size(),
|
||||
'file_name' => $this->get_file_name(),
|
||||
'upload_key' => $this->get_key(),
|
||||
'error' => $e->getCode() . ': ' . $e->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the status of the upload of this attachment
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check_status() {
|
||||
|
||||
if ( $this->is_uploaded() ) {
|
||||
$uploaded_attachment_id = $this->get_uploaded_attachment_id();
|
||||
$uploaded_video_guid = get_post_meta( $uploaded_attachment_id, 'videopress_guid', true );
|
||||
if ( $uploaded_video_guid ) {
|
||||
return array(
|
||||
'status' => 'uploaded',
|
||||
'upload_key' => $this->get_key(),
|
||||
'uploaded_post_id' => $uploaded_attachment_id,
|
||||
'uploaded_video_guid' => $uploaded_video_guid,
|
||||
);
|
||||
} else {
|
||||
// VideoPress attachment is gone, allow user to upload it again.
|
||||
$this->unmark_as_uploaded();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$offset = $this->get_client()->get_offset();
|
||||
$status = false !== $offset ? 'resume' : 'new';
|
||||
$offset = false === $offset ? 0 : $offset;
|
||||
|
||||
return array(
|
||||
'status' => $status,
|
||||
'bytes_uploaded' => $offset,
|
||||
'file_size' => $this->get_file_size(),
|
||||
'file_name' => $this->get_file_name(),
|
||||
'upload_key' => $this->get_key(),
|
||||
);
|
||||
} catch ( File_Exception $e ) {
|
||||
return array(
|
||||
'status' => 'resume',
|
||||
'bytes_uploaded' => 0,
|
||||
'file_size' => $this->get_file_size(),
|
||||
'file_name' => $this->get_file_name(),
|
||||
'upload_key' => $this->get_key(),
|
||||
);
|
||||
} catch ( \Exception $e ) {
|
||||
return array(
|
||||
'status' => 'error',
|
||||
'bytes_uploaded' => -1,
|
||||
'file_size' => $this->get_file_size(),
|
||||
'file_name' => $this->get_file_name(),
|
||||
'message' => $e->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* The Utils class.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* The Utils class.
|
||||
*/
|
||||
class Utils {
|
||||
/**
|
||||
* Build a VideoPress video URL based on the guid and block attributes.
|
||||
*
|
||||
* @param string $guid - Video GUID.
|
||||
* @param array $attributes - Video block attributes. Default is an empty array.
|
||||
*
|
||||
* @return string VideoPress video URL with the specified attributes.
|
||||
*/
|
||||
public static function get_video_press_url( $guid, $attributes = array() ) {
|
||||
if ( ! $guid ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$video_press_url_options = wp_parse_args(
|
||||
$attributes,
|
||||
array(
|
||||
'autoplay' => false,
|
||||
'controls' => true,
|
||||
'loop' => false,
|
||||
'muted' => false,
|
||||
'playsinline' => false,
|
||||
'poster' => '',
|
||||
'preload' => 'metadata',
|
||||
'seekbarColor' => '',
|
||||
'seekbarPlayedColor' => '',
|
||||
'seekbarLoadingColor' => '',
|
||||
'useAverageColor' => true,
|
||||
)
|
||||
);
|
||||
|
||||
$query_args = array(
|
||||
'resizeToParent' => 1,
|
||||
'cover' => 1,
|
||||
'autoPlay' => (int) $video_press_url_options['autoplay'],
|
||||
'controls' => (int) $video_press_url_options['controls'],
|
||||
'loop' => (int) $video_press_url_options['loop'],
|
||||
'muted' => (int) $video_press_url_options['muted'],
|
||||
'persistVolume' => $video_press_url_options['muted'] ? 0 : 1,
|
||||
'playsinline' => (int) $video_press_url_options['playsinline'],
|
||||
'preloadContent' => $video_press_url_options['preload'],
|
||||
'sbc' => $video_press_url_options['seekbarColor'],
|
||||
'sbpc' => $video_press_url_options['seekbarPlayedColor'],
|
||||
'sblc' => $video_press_url_options['seekbarLoadingColor'],
|
||||
'useAverageColor' => (int) $video_press_url_options['useAverageColor'],
|
||||
);
|
||||
|
||||
if ( ! empty( $video_press_url_options['poster'] ) ) {
|
||||
$query_args['posterUrl'] = rawurlencode( $video_press_url_options['poster'] );
|
||||
}
|
||||
|
||||
$url = 'https://videopress.com/v/' . $guid;
|
||||
return add_query_arg( $query_args, $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given URL is a VideoPress URL.
|
||||
*
|
||||
* @since x.x.x
|
||||
*
|
||||
* @param mixed $url The URL to check. Non-strings return false.
|
||||
* @return bool True if the URL is a VideoPress URL, false otherwise.
|
||||
*/
|
||||
public static function is_videopress_url( $url ) {
|
||||
return null !== self::extract_videopress_guid_from_url( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the VideoPress guid from a recognised VideoPress URL.
|
||||
*
|
||||
* @param mixed $url The URL to inspect. Non-strings return null.
|
||||
* @return string|null The 8-character guid, or null if the URL is not a recognised VideoPress URL.
|
||||
*/
|
||||
public static function extract_videopress_guid_from_url( $url ) {
|
||||
if ( ! is_string( $url ) ) {
|
||||
return null;
|
||||
}
|
||||
$pattern = '/^https?:\/\/(?:(?:v(?:ideo)?\.wordpress\.com|videopress\.com)\/(?:v|embed)|v\.wordpress\.com|videos\.(?:videopress\.com|files\.wordpress\.com))\/([a-z\d]{8})(\/|\b)/i'; // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText
|
||||
if ( preg_match( $pattern, $url, $matches ) ) {
|
||||
return $matches[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress video block email renderer class.
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
/**
|
||||
* Handles rendering VideoPress video blocks for email contexts.
|
||||
*/
|
||||
class Video_Block_Email_Renderer {
|
||||
|
||||
/**
|
||||
* Render VideoPress video block for email.
|
||||
*
|
||||
* @param string $block_content The original block HTML content.
|
||||
* @param array $parsed_block The parsed block data including attributes.
|
||||
* @param object $rendering_context Email rendering context.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function render( $block_content, $parsed_block, $rendering_context ) {
|
||||
// Validate input parameters and required dependencies.
|
||||
if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) ||
|
||||
! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Embed' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attributes = $parsed_block['attrs'];
|
||||
|
||||
// Get the VideoPress URL from the guid attribute.
|
||||
$videopress_url = self::get_videopress_url( $attributes );
|
||||
|
||||
if ( empty( $videopress_url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// For private videos, render a simple link to the post since the video isn't accessible on VideoPress.
|
||||
// The isPrivate attribute is pre-computed by the block editor based on video and site settings.
|
||||
if ( isset( $attributes['isPrivate'] ) && true === $attributes['isPrivate'] ) {
|
||||
return self::render_link( $parsed_block );
|
||||
}
|
||||
|
||||
// Create a mock embed block structure that WooCommerce's embed renderer can handle.
|
||||
// The embed renderer will detect VideoPress from the URL and render it appropriately.
|
||||
$mock_embed_block = array(
|
||||
'blockName' => 'core/embed',
|
||||
'attrs' => array(
|
||||
'url' => $videopress_url,
|
||||
'providerNameSlug' => 'videopress',
|
||||
),
|
||||
'innerHTML' => sprintf(
|
||||
'<figure class="wp-block-embed is-type-video is-provider-videopress"><div class="wp-block-embed__wrapper">%s</div></figure>',
|
||||
esc_url( $videopress_url )
|
||||
),
|
||||
);
|
||||
|
||||
// Preserve email_attrs if present (used for spacing/width calculations).
|
||||
if ( ! empty( $parsed_block['email_attrs'] ) ) {
|
||||
$mock_embed_block['email_attrs'] = $parsed_block['email_attrs'];
|
||||
}
|
||||
|
||||
// Use WooCommerce's core embed renderer.
|
||||
// @phan-suppress-next-line PhanUndeclaredClassMethod -- Optional WooCommerce dependency, checked with class_exists() above.
|
||||
$woo_embed_renderer = new \Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Embed();
|
||||
|
||||
// @phan-suppress-next-line PhanUndeclaredClassMethod -- Optional WooCommerce dependency, checked with class_exists() above.
|
||||
return $woo_embed_renderer->render( $mock_embed_block['innerHTML'], $mock_embed_block, $rendering_context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a simple link for private VideoPress videos in emails.
|
||||
* Links to the post containing the video since private videos aren't accessible on VideoPress.
|
||||
*
|
||||
* @param array $parsed_block The parsed block data.
|
||||
*
|
||||
* @return string The rendered link HTML.
|
||||
*/
|
||||
private static function render_link( $parsed_block ) {
|
||||
$post_url = get_the_permalink();
|
||||
|
||||
if ( empty( $post_url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$link_text = __( 'Visit the post to watch the video', 'jetpack-videopress-pkg' );
|
||||
|
||||
$link_html = sprintf(
|
||||
'<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
|
||||
esc_url( $post_url ),
|
||||
esc_html( $link_text )
|
||||
);
|
||||
|
||||
// Wrap with spacing if email_attrs are present.
|
||||
$email_attrs = $parsed_block['email_attrs'] ?? array();
|
||||
if ( ! empty( $email_attrs['padding'] ) ) {
|
||||
$link_html = sprintf(
|
||||
'<p style="padding: %s;">%s</p>',
|
||||
esc_attr( $email_attrs['padding'] ),
|
||||
$link_html
|
||||
);
|
||||
}
|
||||
|
||||
return $link_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the VideoPress URL from block attributes for email rendering.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
*
|
||||
* @return string VideoPress URL or empty string.
|
||||
*/
|
||||
private static function get_videopress_url( $attributes ) {
|
||||
// Construct URL from guid attribute.
|
||||
if ( ! empty( $attributes['guid'] ) ) {
|
||||
$guid = $attributes['guid'];
|
||||
|
||||
// VideoPress guids are alphanumeric only (e.g., "nfbj0J36").
|
||||
// Validate format to prevent any injection issues.
|
||||
if ( preg_match( '/^[a-zA-Z0-9]+$/', $guid ) ) {
|
||||
return 'https://videopress.com/v/' . $guid;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Features Endpoint
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use Automattic\Jetpack\My_Jetpack\Product;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
/**
|
||||
* VideoPress REST API class for fetching site features from WPCOM.
|
||||
*/
|
||||
class VideoPress_Rest_Api_V1_Features {
|
||||
/**
|
||||
* Initializes the endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'rest_api_init', array( static::class, 'register_rest_endpoints' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route(
|
||||
'videopress/v1',
|
||||
'features',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => static::class . '::get_features',
|
||||
'permission_callback' => static::class . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user has permissions to see the features.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'read' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns VideoPress feature flags fetched fresh from WPCOM.
|
||||
*
|
||||
* Uses My Jetpack's Product::get_site_features_from_wpcom() which calls
|
||||
* WPCOM /sites/%d/features API with a 15-second transient cache.
|
||||
*
|
||||
* @return \WP_REST_Response The response object.
|
||||
*/
|
||||
public static function get_features() {
|
||||
$features = Product::get_site_features_from_wpcom();
|
||||
|
||||
if ( is_wp_error( $features ) ) {
|
||||
return rest_ensure_response( $features );
|
||||
}
|
||||
|
||||
$active = $features['active'] ?? array();
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'isVideoPressSupported' => true, // Always true due to free tier.
|
||||
// Check videopress-1tb-storage (Jetpack) or videopress (WordPress.com).
|
||||
'isVideoPress1TBSupported' => in_array( 'videopress-1tb-storage', $active, true )
|
||||
|| ( ( new Host() )->is_wpcom_platform() && in_array( 'videopress', $active, true ) ),
|
||||
'isVideoPressUnlimitedSupported' => in_array( 'videopress-unlimited-storage', $active, true ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Settings Endpoint
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Rest API class for fetching and setting site settings related to VideoPress.
|
||||
*/
|
||||
class VideoPress_Rest_Api_V1_Settings {
|
||||
/**
|
||||
* Initializes the endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'rest_api_init', array( static::class, 'register_rest_endpoints' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route(
|
||||
'videopress/v1',
|
||||
'settings',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( static::class, 'get_settings' ),
|
||||
'permission_callback' => function () {
|
||||
return current_user_can( 'manage_options' );
|
||||
},
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( static::class, 'update_settings' ),
|
||||
'permission_callback' => function () {
|
||||
return Data::can_perform_action() && current_user_can( 'manage_options' );
|
||||
},
|
||||
'args' => array(
|
||||
'videopress_videos_private_for_site' => array(
|
||||
'description' => __( 'If the VideoPress videos should be private by default', 'jetpack-videopress-pkg' ),
|
||||
'type' => 'boolean',
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the VideoPress settings.
|
||||
*
|
||||
* @return WP_Rest_Response - The response object.
|
||||
*/
|
||||
public static function get_settings() {
|
||||
$has_connected_owner = Data::has_connected_owner();
|
||||
if ( ! $has_connected_owner ) {
|
||||
return rest_ensure_response(
|
||||
new WP_Error(
|
||||
'owner_not_connected',
|
||||
'User not connected.',
|
||||
array(
|
||||
'code' => 503,
|
||||
'connect_url' => Admin_UI::get_admin_page_url(),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$blog_id = Data::get_blog_id();
|
||||
if ( ! $blog_id ) {
|
||||
return rest_ensure_response(
|
||||
new WP_Error( 'site_not_registered', 'Site not registered.', 503 )
|
||||
);
|
||||
}
|
||||
|
||||
$status = 200;
|
||||
$data = Data::get_videopress_settings();
|
||||
|
||||
return rest_ensure_response(
|
||||
new WP_REST_Response( $data, $status )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value of the VideoPress settings when a new value
|
||||
* is present on the request body.
|
||||
*
|
||||
* @param WP_REST_Request $request the request object.
|
||||
* @return WP_Rest_Response - The response object.
|
||||
*/
|
||||
public static function update_settings( $request ) {
|
||||
$has_connected_owner = Data::has_connected_owner();
|
||||
if ( ! $has_connected_owner ) {
|
||||
return rest_ensure_response(
|
||||
new WP_Error(
|
||||
'owner_not_connected',
|
||||
'User not connected.',
|
||||
array(
|
||||
'code' => 503,
|
||||
'connect_url' => Admin_UI::get_admin_page_url(),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$blog_id = Data::get_blog_id();
|
||||
if ( ! $blog_id ) {
|
||||
return rest_ensure_response(
|
||||
new WP_Error( 'site_not_registered', 'Site not registered.', 503 )
|
||||
);
|
||||
}
|
||||
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
// We are sure that the param is set because it's required by the request
|
||||
update_option( 'videopress_private_enabled_for_site', boolval( $json_params['videopress_videos_private_for_site'] ) );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
'message' => __( 'VideoPress settings updated successfully.', 'jetpack-videopress-pkg' ),
|
||||
'data' => 200,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Site Info Endpoint
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* VideoPress rest api class for fetching site information
|
||||
*/
|
||||
class VideoPress_Rest_Api_V1_Site {
|
||||
/**
|
||||
* Initializes the endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'rest_api_init', array( static::class, 'register_rest_endpoints' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
register_rest_route(
|
||||
'videopress/v1',
|
||||
'site',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => static::class . '::get_site_info',
|
||||
'permission_callback' => static::class . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the user have permissions to see the site info
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'read' ); // TODO: confirm this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the site information usually provided by Jetpack, without relying on Jetpack
|
||||
*
|
||||
* @return WP_Rest_Response The response object.
|
||||
*/
|
||||
public static function get_site_info() {
|
||||
$data = Site::get_site_info();
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* VideoPress Stats Endpoints
|
||||
*
|
||||
* @package automattic/jetpack-videopress
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\VideoPress;
|
||||
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* VideoPress stats rest api class
|
||||
*/
|
||||
class VideoPress_Rest_Api_V1_Stats {
|
||||
/**
|
||||
* Initializes the endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'rest_api_init', array( static::class, 'register_rest_endpoints' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST API routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_rest_endpoints() {
|
||||
/**
|
||||
* Basic stats.
|
||||
*/
|
||||
register_rest_route(
|
||||
'videopress/v1',
|
||||
'stats',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => static::class . '::get_stats',
|
||||
'permission_callback' => static::class . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Stats to be featured by My Jetpack.
|
||||
*/
|
||||
register_rest_route(
|
||||
'videopress/v1',
|
||||
'stats/featured',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => static::class . '::get_featured_stats',
|
||||
'permission_callback' => static::class . '::permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the user have permissions to see stats
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function permissions_callback() {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for getting the general VideoPress stats for the site.
|
||||
*
|
||||
* Returns the plays for all the videos, for today and since the beggining of times.
|
||||
*
|
||||
* @return WP_Rest_Response - The response object.
|
||||
*/
|
||||
public static function get_stats() {
|
||||
$today_plays = Stats::get_today_plays();
|
||||
|
||||
if ( is_wp_error( $today_plays ) ) {
|
||||
// TODO: Improve status code.
|
||||
return rest_ensure_response( $today_plays );
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'plays' => array(
|
||||
'today' => $today_plays,
|
||||
),
|
||||
);
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the stats that will be featured by My Jetpack.
|
||||
*
|
||||
* @return WP_Rest_Response - The response object.
|
||||
*/
|
||||
public static function get_featured_stats() {
|
||||
$featured_stats = Stats::get_featured_stats();
|
||||
return rest_ensure_response( $featured_stats );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user