deleted plugin Infinite Uploads
version 2.0.8
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime'), 'version' => '4dd50626eed3ac988f73');
|
@ -1 +0,0 @@
|
||||
(()=>{"use strict";var t={790:t=>{t.exports=window.ReactJSXRuntime}},r={},e=function e(o){var i=r[o];if(void 0!==i)return i.exports;var n=r[o]={exports:{}};return t[o](n,n.exports,e),n.exports}(790);window.ReactJSXRuntime=e})();
|
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-element', 'wp-i18n'), 'version' => '83c76c2a8f08d59cdbc2');
|
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '0b0da79b86a9e717ae3e');
|
@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "uglyrobot/infinite-uploads",
|
||||
"description": "WordPress plugin to store uploads in the cloud and serve via the Infinite Uploads CDN.",
|
||||
"homepage": "https://github.com/uglyrobot/infinite-uploads",
|
||||
"keywords": [
|
||||
"wordpress",
|
||||
"cloud",
|
||||
"storage",
|
||||
"s3",
|
||||
"cdn",
|
||||
"plugin"
|
||||
],
|
||||
"license": "GPL-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "UglyRobot, LLC",
|
||||
"email": "contact@uglyrobot.com",
|
||||
"homepage": "https://uglyrobot.com/"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/uglyrobot/infinite-uploads/issues",
|
||||
"source": "https://github.com/uglyrobot/infinite-uploads"
|
||||
},
|
||||
"type": "wordpress-plugin",
|
||||
"require": {
|
||||
"composer/installers": "~1.0"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "5.6"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,325 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors
|
||||
* Copyright 2011-2021 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus:not(:focus-visible) {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([class]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[type="button"]:not(:disabled),
|
||||
[type="reset"]:not(:disabled),
|
||||
[type="submit"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
@ -1,8 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors
|
||||
* Copyright 2011-2021 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
@ -1,253 +0,0 @@
|
||||
body {
|
||||
background-color: rgb(241, 241, 241);
|
||||
}
|
||||
|
||||
/* Fixes conflict with woosquared */
|
||||
.modal {
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
/* Fixes conflict with wp core on mobile hidding modal under background fade */
|
||||
body.mobile.modal-open #wpwrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-top: inherit;
|
||||
padding: 0;
|
||||
min-width: inherit;
|
||||
max-width: 100%;
|
||||
box-shadow: inherit;
|
||||
background: inherit;
|
||||
box-sizing: inherit;
|
||||
background-color: #ffffff;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.card-body.cloud {
|
||||
background: bottom url("../img/wave-bg.svg") no-repeat;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
background-color: rgba(38, 169, 224, 1);
|
||||
}
|
||||
|
||||
.badge-primary:hover, .badge-primary:active, .badge-primary:focus {
|
||||
background-color: rgba(38, 169, 224, 0.7);
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.badge-primary:active {
|
||||
background-color: rgba(38, 169, 224, 0.9);
|
||||
}
|
||||
|
||||
.btn-group-lg > .btn, .btn-lg {
|
||||
padding: .5rem 2rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: rgba(38, 169, 224, 1);
|
||||
border-radius: 24px;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.btn-primary:hover, .btn-primary:active, .btn-primary:focus {
|
||||
background-color: rgba(38, 169, 224, 0.7) !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
background-color: rgba(38, 169, 224, 0.9) !important;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background-color: #EE7C1E;
|
||||
border-radius: 24px;
|
||||
border-width: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-info:hover, .btn-info:active, .btn-info:focus {
|
||||
background-color: rgba(238, 124, 30, 0.7) !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.btn-info:active {
|
||||
background-color: rgba(238, 124, 30, 0.9) !important;
|
||||
}
|
||||
|
||||
.btn .dashicons {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-lg .dashicons {
|
||||
font-size: 1.8rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
#iup-settings-page .progress {
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#iup-settings-page .progress .progress-bar {
|
||||
background-color: rgba(38, 169, 224, 1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#iup-settings-page .progress.download .progress-bar {
|
||||
background-color: #EE7C1E;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-inner {
|
||||
background-color: #f1f1f1;
|
||||
color: #2A2A2A;
|
||||
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.bs-tooltip-top .arrow {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.bs-tooltip-top .arrow:before {
|
||||
border-top-color: #F1F1F1;
|
||||
}
|
||||
|
||||
.bs-tooltip-bottom .arrow:before {
|
||||
border-bottom-color: #F1F1F1;
|
||||
}
|
||||
|
||||
.mb-lc-0 > :last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
#iup-sync-errors, #iup-download-errors {
|
||||
height: 3em;
|
||||
display: none;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.iup-enabled-status .dashicons {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.iup-enabled-status .dashicons-cloud-saved, .iup-enabled-status .dashicons-video-alt3 {
|
||||
color: rgba(38, 169, 224, 1);
|
||||
}
|
||||
|
||||
p.lead {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#iup-footer .text-muted {
|
||||
color: rgba(108, 117, 125, 0.5) !important;
|
||||
}
|
||||
|
||||
.iup-refresh-icon .dashicons {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.text-warning, .text-warning a {
|
||||
color: #EE7C1E !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#iup-error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#iup-scan-progress, #iup-scan-remote-progress, #iup-upload-progress, #iup-download-progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#iup-local-pie, #iup-cloud-pie, .iup-pie-wrapper {
|
||||
max-height: 300px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
#iup-enable-errors .alert-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#iup-enable-errors .alert-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#iup-enable-errors .dashicons {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#iup-collapse-errors {
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#iup-collapse-errors .list-group-item {
|
||||
padding: 0.25rem 1.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* --------- Stream Settings--------- */
|
||||
#stream-nav-tab {
|
||||
margin-bottom: -0.8rem;
|
||||
}
|
||||
|
||||
li.nav-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-check .form-check-input {
|
||||
margin-top: .25em;
|
||||
}
|
||||
|
||||
.iup-video-thumb {
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.uppy-StatusBar {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.uppy-StatusBar-progress {
|
||||
height: 1em;
|
||||
background-color: #26a9e0;
|
||||
}
|
||||
|
||||
.uppy-StatusBar::before {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.uppy-StatusBar-content {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.uppy-StatusBar-actions {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.uppy-DragDrop-inner {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.uppy-DragDrop-arrow {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 101.61 54.71">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#26a9e0;}</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M448.81,513.94a3.91,3.91,0,0,0,5.53,5.52L477.79,496a3.92,3.92,0,0,0,0-5.53L454.34,467a3.91,3.91,0,0,0-5.53,5.53l16.78,16.77H381.23a3.91,3.91,0,0,0,0,7.82h84.36Z" transform="translate(-377.32 -465.9)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 395 B |
@ -1,7 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156.32 156.32">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#26a9e0;}.cls-2{fill:#4bc2ec;}</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M448.81,472.57a3.89,3.89,0,0,0,2.76,1.14,3.91,3.91,0,0,0,2.77-6.67l-23.45-23.45a3.92,3.92,0,0,0-5.53,0L401.91,467a3.91,3.91,0,0,0,5.53,5.53l16.78-16.78v84.36a3.91,3.91,0,1,0,7.81,0V455.79Z" transform="scale(1, -1) translate(-349.97 -597.74)"/>
|
||||
<path class="cls-2" d="M469.44,419.44a42.77,42.77,0,0,0-82.62,0A43,43,0,0,0,393,505H416.4v-7.82H393a35.17,35.17,0,0,1,0-70.34c.14,0,.26,0,.39,0a35,35,0,0,1,69.57,0c.13,0,.26,0,.39,0a35.17,35.17,0,0,1,0,70.34H439.85V505H463.3a43,43,0,0,0,6.14-85.54Z" transform="translate(-349.97 -387.74)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 752 B |
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="36" height="18" viewBox="0 0 36 18" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="Path" fill="#ffffff" stroke="none"
|
||||
d="M 13.049364 16.662685 C 10.227212 16.658094 7.922546 14.405847 7.853045 11.584545 C 7.783543 8.763247 9.974511 6.400249 12.793015 6.256704 C 12.988648 3.558298 15.347159 1.337301 18.101088 1.337301 C 19.445942 1.336185 20.737968 1.860779 21.701384 2.79911 C 22.626104 3.693738 23.228056 4.959394 23.354156 6.272271 C 25.999199 6.515739 28.036863 8.711359 28.082537 11.367191 C 28.128214 14.023025 26.167253 16.287415 23.532146 16.621691 L 23.513466 16.621691 L 21.151323 16.651789 L 17.827097 13.311993 C 17.439978 13.882811 16.958418 14.540285 16.413548 15.092939 C 15.374143 16.149988 14.273505 16.662685 13.049364 16.662685 Z M 12.680408 8.144031 C 10.915305 8.331511 9.607052 9.869812 9.705452 11.642115 C 9.803851 13.414416 11.274353 14.798439 13.049364 14.789371 C 14.457723 14.789371 15.519959 13.424082 16.478933 11.960196 Z M 23.245182 14.769132 C 24.994526 14.56369 26.282291 13.032967 26.185333 11.274271 C 26.088377 9.515574 24.640146 8.135688 22.87882 8.123792 C 22.115484 8.123792 21.425833 8.464206 20.707644 9.195889 C 20.210514 9.701839 19.75905 10.337521 19.371414 10.919234 Z M 18.018579 9.574183 C 18.474194 8.908924 19.052275 8.144548 19.731028 7.538445 C 20.295616 7.035089 20.87266 6.686373 21.484993 6.478284 L 21.484993 6.421202 C 21.397455 5.559499 21.013577 4.754884 20.398884 4.144683 C 19.784058 3.545609 18.959517 3.210427 18.101088 3.210618 C 17.218918 3.210618 16.336746 3.581649 15.682903 4.228748 C 15.12676 4.76929 14.771171 5.483032 14.674633 6.252551 Z"/>
|
||||
<path id="path1" fill="#4bc2ec" stroke="none"
|
||||
d="M 18.101088 1.674604 C 19.358915 1.673174 20.567423 2.163727 21.468388 3.041449 C 22.336546 3.881588 22.902174 5.07148 23.019968 6.306001 L 23.019968 6.311709 L 23.041763 6.590372 C 25.580084 6.673662 27.628056 8.693946 27.745895 11.230894 C 27.863737 13.767843 26.011856 15.969273 23.492189 16.287504 L 21.291952 16.316044 L 17.777281 12.787361 C 17.281189 13.537207 16.75967 14.2637 16.175362 14.858904 C 15.201859 15.84486 14.180098 16.325384 13.05092 16.325384 C 11.299487 16.343466 9.673302 15.419453 8.792324 13.905609 C 7.911346 12.391765 7.911346 10.521396 8.792324 9.007554 C 9.673302 7.49371 11.299487 6.569695 13.05092 6.587776 L 13.118899 6.587776 C 13.137061 3.921026 15.412025 1.674604 18.100571 1.674604 M 18.069952 10.101412 C 18.62105 9.268538 19.232342 8.435665 19.955723 7.790124 C 20.551447 7.259265 21.16741 6.907953 21.819696 6.728405 L 21.819696 6.64434 L 21.819696 6.405115 C 21.724735 5.455485 21.291431 4.544255 20.629284 3.903381 C 19.951849 3.242846 19.0431 2.873201 18.096937 2.873318 C 17.127068 2.873318 16.159275 3.280154 15.441602 3.989006 C 14.775824 4.647001 14.380923 5.490771 14.320209 6.376056 L 18.06632 10.099336 M 23.114412 15.116811 C 25.081905 14.976645 26.586926 13.30669 26.522467 11.335262 C 26.45801 9.363835 24.847118 7.795759 22.874672 7.784416 C 22.014812 7.784416 21.248363 8.157003 20.462711 8.957702 C 19.905388 9.525406 19.399956 10.24723 18.934481 10.962306 L 23.114412 15.116811 M 13.047288 15.124597 C 14.660623 15.124597 15.789801 13.646698 16.908602 11.913492 L 12.806505 7.7922 C 10.826563 7.916845 9.304119 9.592428 9.36923 11.575222 C 9.43434 13.558018 11.063432 15.130111 13.047288 15.124597 M 18.101088 1.000002 C 17.353039 0.999603 16.612734 1.151443 15.92524 1.446278 C 14.57925 2.021828 13.500736 3.085141 12.906139 4.422825 C 12.692595 4.905839 12.55031 5.417297 12.483735 5.941197 C 9.541832 6.235274 7.34853 8.786498 7.499138 11.739223 C 7.649746 14.691952 10.091249 17.006769 13.047807 16.999985 C 14.366911 16.999985 15.545906 16.454596 16.652252 15.332166 C 17.098476 14.868431 17.506634 14.369516 17.872763 13.840258 L 20.809872 16.79138 L 21.011732 16.993759 L 21.297142 16.990126 L 23.497379 16.961586 L 23.535259 16.961586 L 23.572622 16.956917 C 26.342112 16.603355 28.418348 14.248287 28.421967 11.45632 C 28.421238 9.978979 27.825266 8.564247 26.768675 7.531699 C 25.925209 6.69041 24.833696 6.142469 23.655132 5.9687 C 23.471434 4.675543 22.853394 3.444134 21.935417 2.556777 C 20.909227 1.557362 19.533012 0.998684 18.100571 1.000002 Z M 15.033729 6.131124 C 15.150015 5.498928 15.460137 4.918583 15.921089 4.470567 C 16.513699 3.885221 17.308172 3.549476 18.100571 3.549476 C 18.871525 3.549932 19.611835 3.851383 20.163811 4.389615 C 20.672724 4.894178 21.008478 5.54716 21.122782 6.254628 C 20.561306 6.482435 20.028372 6.825444 19.506855 7.288323 C 18.913206 7.817627 18.398432 8.457978 17.971355 9.052665 L 15.033729 6.131124 Z M 19.807829 10.876164 C 20.149799 10.373846 20.534323 9.854403 20.949461 9.432 C 21.608496 8.760512 22.20422 8.461092 22.87986 8.461092 C 24.439234 8.461831 25.737265 9.658706 25.864241 11.212904 C 25.991215 12.767105 24.904671 14.158794 23.366091 14.412631 L 19.807829 10.876164 Z M 13.049364 14.45207 C 11.496465 14.443489 10.207574 13.249674 10.080254 11.701982 C 9.952933 10.154289 11.0294 8.765889 12.560017 8.503644 L 16.04615 12.005861 C 15.628415 12.628571 15.181623 13.233118 14.703694 13.681987 C 14.142735 14.209213 13.617066 14.454664 13.049364 14.454664 Z"/>
|
||||
<path id="path2" fill="#26a9e0" stroke="none" d="M 22.296587 16.022852 L 18.339792 12.066057 L 18.014427 12.564224 L 21.473057 16.022852 L 22.296587 16.022852 Z"/>
|
||||
<path id="path3" fill="#26a9e0" stroke="none"
|
||||
d="M 22.878822 16.017143 L 22.758949 16.017143 L 18.223555 11.509769 L 18.128073 11.661297 C 16.775238 13.802896 15.377776 16.017143 13.049364 16.017143 C 11.408439 16.034649 9.884616 15.169258 9.059059 13.751022 C 8.233502 12.332785 8.233502 10.580378 9.059059 9.16214 C 9.884616 7.743904 11.408439 6.878513 13.049364 6.896017 L 13.169753 6.896017 L 17.381859 11.107605 L 17.050266 11.599545 L 12.93053 7.479807 C 10.743531 7.525926 9.004097 9.329171 9.03677 11.516411 C 9.069444 13.703652 10.861963 15.454137 13.049364 15.434912 C 15.055525 15.434912 16.308725 13.450545 17.635614 11.35046 L 17.800631 11.090998 L 13.425063 6.739303 L 13.425063 6.617875 C 13.425063 4.104727 15.566145 1.981287 18.100571 1.981287 C 19.27766 1.983212 20.407906 2.442641 21.252514 3.262512 C 22.095243 4.077741 22.60483 5.219374 22.711727 6.335061 L 22.73041 6.574803 C 22.527668 6.55539 22.32312 6.579632 22.130535 6.645895 L 22.130535 6.390587 C 22.039204 5.410856 21.591888 4.404144 20.845678 3.681801 C 20.109468 2.967632 19.124702 2.567249 18.099012 2.565077 C 17.04871 2.565077 16.002041 3.004087 15.227287 3.769499 C 14.469138 4.518309 14.03895 5.481949 14.007816 6.500079 L 18.119251 10.587123 C 19.364668 8.667105 20.729956 6.896017 22.876745 6.896017 C 24.51767 6.878513 26.041492 7.743904 26.86705 9.16214 C 27.692608 10.580378 27.692608 12.332785 26.86705 13.751022 C 26.041492 15.169258 24.51767 16.034649 22.876745 16.017143 Z M 18.543732 11.006933 L 22.997135 15.432837 C 25.14624 15.366451 26.854794 13.606451 26.85741 11.456321 C 26.833828 9.268935 25.06621 7.501552 22.878822 7.47825 C 21.930746 7.47825 21.094242 7.880416 20.24684 8.743906 C 19.633989 9.368172 19.077185 10.177174 18.543732 11.006933 Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.0 KiB |
@ -1,15 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 403.08 308.33">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#fff;}.cls-2{fill:#4bc2ec;}.cls-3{fill:#26a9e0;}</style>
|
||||
</defs>
|
||||
<path class="cls-1"
|
||||
d="M305.76,547.23a100.33,100.33,0,0,1-4.94-200.53c3.77-52,49.22-94.8,102.29-94.8a99.32,99.32,0,0,1,69.38,28.17c17.82,17.24,29.42,41.63,31.85,66.93a100.33,100.33,0,0,1,3.43,199.44l-.36,0-45.52.58-64.06-64.36c-7.46,11-16.74,23.67-27.24,34.32C350.56,537.35,329.35,547.23,305.76,547.23Zm-7.11-164.16a64.21,64.21,0,0,0,7.11,128.06c27.14,0,47.61-26.31,66.09-54.52ZM502.24,510.74a64.25,64.25,0,0,0-7.06-128.06c-14.71,0-28,6.56-41.84,20.66-9.58,9.75-18.28,22-25.75,33.21ZM401.52,410.63c8.78-12.82,19.92-27.55,33-39.23,10.88-9.7,22-16.42,33.8-20.43v-1.1A72.08,72.08,0,0,0,447.39,306a63.43,63.43,0,0,0-44.28-18c-17,0-34,7.15-46.6,19.62a65.81,65.81,0,0,0-19.43,39Z"
|
||||
transform="translate(-198.93 -245.4)"/>
|
||||
<path class="cls-2"
|
||||
d="M403.11,258.4A92.84,92.84,0,0,1,468,284.74c16.73,16.19,27.63,39.12,29.9,62.91v.11l.42,5.37A93.83,93.83,0,0,1,507,540l-42.4.55-67.73-68C387.31,487,377.26,501,366,512.47c-18.76,19-38.45,28.26-60.21,28.26a93.83,93.83,0,1,1,0-187.65h1.31c.35-51.39,44.19-94.68,96-94.68m-.59,162.39c10.62-16.05,22.4-32.1,36.34-44.54,11.48-10.23,23.35-17,35.92-20.46l0-1.62,0-4.61c-1.83-18.3-10.18-35.86-22.94-48.21a69.89,69.89,0,0,0-48.8-19.85c-18.69,0-37.34,7.84-51.17,21.5-12.83,12.68-20.44,28.94-21.61,46l72.19,71.75m97.28,96.69a70.74,70.74,0,0,0-4.62-141.3c-16.57,0-31.34,7.18-46.48,22.61-10.74,10.94-20.48,24.85-29.45,38.63l80.55,80.06m-194,.15c31.09,0,52.85-28.48,74.41-61.88l-79.05-79.42a70.72,70.72,0,0,0,4.64,141.3M403.11,245.4A106.24,106.24,0,0,0,361.18,254,111.75,111.75,0,0,0,303,311.36a105.14,105.14,0,0,0-8.14,29.26,106.82,106.82,0,0,0,10.87,213.11c25.42,0,48.14-10.51,69.46-32.14a231,231,0,0,0,23.52-28.75l56.6,56.87,3.89,3.9,5.5-.07,42.4-.55h.73l.72-.09A107,107,0,0,0,602,446.9a105.82,105.82,0,0,0-31.86-75.63,107.08,107.08,0,0,0-60-30.12C506.6,316.23,494.69,292.5,477,275.4a105.78,105.78,0,0,0-73.9-30ZM344,344.28a59.68,59.68,0,0,1,17.1-32c11.42-11.28,26.73-17.75,42-17.75a57,57,0,0,1,39.76,16.19,65.27,65.27,0,0,1,18.48,35.94c-10.82,4.39-21.09,11-31.14,19.92-11.44,10.2-21.36,22.54-29.59,34L344,344.28Zm92,91.44c6.59-9.68,14-19.69,22-27.83,12.7-12.94,24.18-18.71,37.2-18.71a57.73,57.73,0,0,1,9.37,114.69L436,435.72ZM305.76,504.63A57.73,57.73,0,0,1,296.33,390l67.18,67.49c-8.05,12-16.66,23.65-25.87,32.3-10.81,10.16-20.94,14.89-31.88,14.89Z"
|
||||
transform="translate(-198.93 -245.4)"/>
|
||||
<polygon class="cls-3" points="285.03 289.5 208.78 213.25 202.51 222.85 269.16 289.5 285.03 289.5"/>
|
||||
<path class="cls-3"
|
||||
d="M495.18,534.79h-2.31l-87.4-86.86-1.84,2.92c-26.07,41.27-53,83.94-97.87,83.94a87.89,87.89,0,1,1,0-175.77h2.32l81.17,81.16-6.39,9.48-79.39-79.39a76.66,76.66,0,0,0,2.29,153.3c38.66,0,62.81-38.24,88.38-78.71l3.18-5L313,356v-2.34c0-48.43,41.26-89.35,90.1-89.35A87.41,87.41,0,0,1,463.84,289c16.24,15.71,26.06,37.71,28.12,59.21l.36,4.62a27.48,27.48,0,0,0-11.56,1.37l0-4.92C479,330.4,470.38,311,456,297.08a76.25,76.25,0,0,0-52.93-21.52c-20.24,0-40.41,8.46-55.34,23.21-14.61,14.43-22.9,33-23.5,52.62l79.23,78.76c24-37,50.31-71.13,91.68-71.13a87.89,87.89,0,1,1,0,175.77Zm-83.54-96.55,85.82,85.29a76.76,76.76,0,0,0,74.39-76.63,77.5,77.5,0,0,0-76.67-76.66c-18.27,0-34.39,7.75-50.72,24.39C432.65,406.66,421.92,422.25,411.64,438.24Z"
|
||||
transform="translate(-198.93 -245.4)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.4 KiB |
@ -1,12 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 403.08 308.33">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#9fa3a8;}</style>
|
||||
</defs>
|
||||
<path class="cls-1"
|
||||
d="M403.11,258.4A92.84,92.84,0,0,1,468,284.74c16.73,16.19,27.63,39.12,29.9,62.91v.11l.42,5.37A93.83,93.83,0,0,1,507,540l-42.4.55-67.73-68C387.31,487,377.26,501,366,512.47c-18.76,19-38.45,28.26-60.21,28.26a93.83,93.83,0,1,1,0-187.65h1.31c.35-51.39,44.19-94.68,96-94.68m-.59,162.39c10.62-16.05,22.4-32.1,36.34-44.54,11.48-10.23,23.35-17,35.92-20.46l0-1.62,0-4.61c-1.83-18.3-10.18-35.86-22.94-48.21a69.89,69.89,0,0,0-48.8-19.85c-18.69,0-37.34,7.84-51.17,21.5-12.83,12.68-20.44,28.94-21.61,46l72.19,71.75m97.28,96.69a70.74,70.74,0,0,0-4.62-141.3c-16.57,0-31.34,7.18-46.48,22.61-10.74,10.94-20.48,24.85-29.45,38.63l80.55,80.06m-194,.15c31.09,0,52.85-28.48,74.41-61.88l-79.05-79.42a70.72,70.72,0,0,0,4.64,141.3M403.11,245.4A106.24,106.24,0,0,0,361.18,254,111.75,111.75,0,0,0,303,311.36a105.14,105.14,0,0,0-8.14,29.26,106.82,106.82,0,0,0,10.87,213.11c25.42,0,48.14-10.51,69.46-32.14a231,231,0,0,0,23.52-28.75l56.6,56.87,3.89,3.9,5.5-.07,42.4-.55h.73l.72-.09A107,107,0,0,0,602,446.9a105.82,105.82,0,0,0-31.86-75.63,107.08,107.08,0,0,0-60-30.12C506.6,316.23,494.69,292.5,477,275.4a105.78,105.78,0,0,0-73.9-30ZM344,344.28a59.68,59.68,0,0,1,17.1-32c11.42-11.28,26.73-17.75,42-17.75a57,57,0,0,1,39.76,16.19,65.27,65.27,0,0,1,18.48,35.94c-10.82,4.39-21.09,11-31.14,19.92-11.44,10.2-21.36,22.54-29.59,34L344,344.28Zm92,91.44c6.59-9.68,14-19.69,22-27.83,12.7-12.94,24.18-18.71,37.2-18.71a57.73,57.73,0,0,1,9.37,114.69L436,435.72ZM305.76,504.63A57.73,57.73,0,0,1,296.33,390l67.18,67.49c-8.05,12-16.66,23.65-25.87,32.3-10.81,10.16-20.94,14.89-31.88,14.89Z"
|
||||
transform="translate(-198.93 -245.4)"/>
|
||||
<polygon class="cls-1" points="285.03 289.5 208.78 213.25 202.51 222.85 269.16 289.5 285.03 289.5"/>
|
||||
<path class="cls-1"
|
||||
d="M495.18,534.79h-2.31l-87.4-86.86-1.84,2.92c-26.07,41.27-53,83.94-97.87,83.94a87.89,87.89,0,1,1,0-175.77h2.32l81.17,81.16-6.39,9.48-79.39-79.39a76.66,76.66,0,0,0,2.29,153.3c38.66,0,62.81-38.24,88.38-78.71l3.18-5L313,356v-2.34c0-48.43,41.26-89.35,90.1-89.35A87.41,87.41,0,0,1,463.84,289c16.24,15.71,26.06,37.71,28.12,59.21l.36,4.62a27.48,27.48,0,0,0-11.56,1.37l0-4.92C479,330.4,470.38,311,456,297.08a76.25,76.25,0,0,0-52.93-21.52c-20.24,0-40.41,8.46-55.34,23.21-14.61,14.43-22.9,33-23.5,52.62l79.23,78.76c24-37,50.31-71.13,91.68-71.13a87.89,87.89,0,1,1,0,175.77Zm-83.54-96.55,85.82,85.29a76.76,76.76,0,0,0,74.39-76.63,77.5,77.5,0,0,0-76.67-76.66c-18.27,0-34.39,7.75-50.72,24.39C432.65,406.66,421.92,422.25,411.64,438.24Z"
|
||||
transform="translate(-198.93 -245.4)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.6 KiB |
@ -1,439 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 200 50" style="enable-background:new 0 0 200 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#4BC2EC;}
|
||||
.st2{fill:#26A9E0;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M18.05,45.44c-3.9,0-7.55-1.5-10.26-4.24C5.09,38.49,3.6,34.87,3.6,31c0-7.73,6.1-14.06,13.74-14.43
|
||||
c0.54-7.49,7.09-13.65,14.73-13.65c3.74,0,7.29,1.44,9.99,4.06c2.56,2.48,4.24,5.99,4.59,9.64c3.29,0.31,6.4,1.77,8.82,4.16
|
||||
c2.78,2.75,4.31,6.38,4.31,10.22c0,7.26-5.43,13.43-12.63,14.33l-0.05,0.01l-6.55,0.08l-9.22-9.27c-1.08,1.59-2.41,3.41-3.92,4.94
|
||||
C24.5,44.02,21.45,45.44,18.05,45.44z M17.03,21.81c-2.12,0.23-4.1,1.2-5.6,2.75C9.73,26.29,8.8,28.58,8.8,31
|
||||
c0,2.49,0.95,4.81,2.67,6.55c1.73,1.74,4.07,2.7,6.58,2.7c3.91,0,6.86-3.79,9.52-7.85L17.03,21.81z M46.34,40.19
|
||||
c2.12-0.23,4.1-1.2,5.61-2.75c1.69-1.74,2.62-4.03,2.62-6.44c0-2.44-0.98-4.76-2.77-6.53c-1.77-1.75-4.07-2.72-6.48-2.72
|
||||
c-2.12,0-4.03,0.95-6.03,2.97c-1.38,1.4-2.63,3.16-3.71,4.78L46.34,40.19z M31.84,25.78c1.26-1.85,2.87-3.97,4.75-5.65
|
||||
c1.57-1.4,3.17-2.36,4.87-2.94l0-0.16c-0.25-2.4-1.35-4.7-3.02-6.31c-1.73-1.67-3.99-2.59-6.38-2.59c-2.45,0-4.89,1.03-6.71,2.83
|
||||
c-1.57,1.55-2.56,3.53-2.8,5.61L31.84,25.78z"/>
|
||||
<path class="st1" d="M32.07,3.86c3.49,0,6.81,1.35,9.34,3.79c2.41,2.33,3.98,5.63,4.31,9.06l0,0.01l0.06,0.77
|
||||
c3.37,0.11,6.56,1.5,9.03,3.94c2.6,2.57,4.03,5.97,4.03,9.56c0,6.88-5.16,12.57-11.81,13.4l-6.11,0.08l-9.75-9.8
|
||||
c-1.37,2.09-2.82,4.11-4.44,5.76c-2.7,2.74-5.54,4.07-8.67,4.07c-3.65,0-7.06-1.41-9.6-3.96C5.93,38.01,4.54,34.62,4.54,31
|
||||
c0-7.45,6.06-13.51,13.51-13.51h0.19C18.29,10.09,24.6,3.86,32.07,3.86 M31.98,27.24c1.53-2.31,3.22-4.62,5.23-6.41
|
||||
c1.65-1.47,3.36-2.45,5.17-2.95l0.01-0.23l0-0.66c-0.26-2.64-1.46-5.16-3.3-6.94c-1.9-1.84-4.4-2.86-7.03-2.86
|
||||
c-2.69,0-5.38,1.13-7.37,3.1c-1.85,1.83-2.94,4.17-3.11,6.63L31.98,27.24 M45.99,41.16c2.52-0.16,4.86-1.24,6.63-3.07
|
||||
c1.86-1.91,2.89-4.43,2.89-7.1c0-2.7-1.08-5.25-3.05-7.19c-1.95-1.93-4.48-2.99-7.14-2.99c-2.39,0-4.51,1.03-6.69,3.26
|
||||
c-1.55,1.58-2.95,3.58-4.24,5.56L45.99,41.16 M18.05,41.18c4.48,0,7.61-4.1,10.71-8.91L17.38,20.84c-2.51,0.16-4.86,1.24-6.63,3.07
|
||||
c-1.86,1.91-2.89,4.43-2.89,7.1c0,2.74,1.05,5.29,2.94,7.2C12.72,40.12,15.29,41.18,18.05,41.18 M32.07,1.98
|
||||
c-2.08,0-4.11,0.42-6.04,1.24c-1.85,0.79-3.52,1.91-4.97,3.34c-1.45,1.43-2.59,3.08-3.4,4.92c-0.6,1.36-0.99,2.77-1.17,4.21
|
||||
C8.73,16.48,2.67,23.05,2.67,31c0,4.12,1.58,7.98,4.45,10.87c2.89,2.91,6.77,4.51,10.93,4.51c3.66,0,6.93-1.51,10-4.63
|
||||
c1.27-1.29,2.41-2.76,3.39-4.14l8.15,8.19l0.56,0.56l0.79-0.01l6.11-0.08l0.11,0l0.1-0.01c7.67-0.96,13.45-7.53,13.45-15.26
|
||||
c0-4.1-1.63-7.96-4.59-10.89c-2.39-2.36-5.41-3.87-8.63-4.34c-0.52-3.59-2.23-7.01-4.78-9.47C39.83,3.52,36.05,1.98,32.07,1.98
|
||||
L32.07,1.98z M23.55,16.22c0.3-1.71,1.15-3.32,2.46-4.61c1.64-1.62,3.85-2.56,6.05-2.56c2.14,0,4.17,0.83,5.73,2.33
|
||||
c1.38,1.34,2.33,3.2,2.66,5.18c-1.56,0.63-3.04,1.58-4.48,2.87c-1.65,1.47-3.08,3.25-4.26,4.9L23.55,16.22L23.55,16.22z
|
||||
M36.8,29.39c0.95-1.39,2.02-2.84,3.17-4.01c1.83-1.86,3.48-2.69,5.36-2.69c2.16,0,4.23,0.87,5.82,2.45
|
||||
c1.61,1.59,2.49,3.67,2.49,5.86c0,2.17-0.84,4.23-2.36,5.79c-1.25,1.29-2.86,2.13-4.61,2.41L36.8,29.39L36.8,29.39z M18.05,39.31
|
||||
c-2.26,0-4.36-0.86-5.91-2.43c-1.55-1.56-2.4-3.65-2.4-5.89c0-2.17,0.84-4.23,2.36-5.79c1.25-1.28,2.86-2.12,4.6-2.41l9.67,9.72
|
||||
c-1.16,1.73-2.4,3.4-3.73,4.65C21.08,38.63,19.62,39.31,18.05,39.31L18.05,39.31z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon class="st2" points="43.71,43.67 32.73,32.69 31.83,34.07 41.42,43.67 "/>
|
||||
<path class="st2" d="M45.32,43.65h-0.33L32.41,31.15c-0.09,0.14-0.18,0.28-0.27,0.42c-3.75,5.94-7.63,12.09-14.09,12.09
|
||||
c-3.42,0-6.62-1.32-8.99-3.71C6.69,37.57,5.39,34.39,5.39,31c0-6.98,5.68-12.65,12.65-12.65h0.33l11.69,11.69l-0.92,1.37
|
||||
L17.72,19.96C11.78,20.14,7.01,25.02,7.01,31c0,2.96,1.13,5.74,3.19,7.81c2.07,2.08,4.86,3.23,7.85,3.23
|
||||
c5.57,0,9.04-5.51,12.73-11.33c0.15-0.24,0.31-0.48,0.46-0.72L19.09,17.91v-0.34c0-6.97,5.94-12.87,12.97-12.87
|
||||
c3.15,0,6.31,1.2,8.75,3.55c2.34,2.26,3.75,5.43,4.05,8.53l0.05,0.67c0,0-0.23-0.05-0.78,0c-0.55,0.05-0.88,0.19-0.88,0.19l0-0.71
|
||||
c-0.26-2.72-1.5-5.52-3.56-7.52c-2.13-2.06-4.9-3.1-7.62-3.1c-2.91,0-5.82,1.22-7.97,3.34c-2.1,2.08-3.3,4.76-3.38,7.58
|
||||
l11.41,11.34c3.45-5.33,7.24-10.24,13.2-10.24c3.31,0,6.46,1.32,8.88,3.7c2.44,2.41,3.78,5.59,3.78,8.95
|
||||
C57.98,37.98,52.3,43.65,45.32,43.65z M33.3,29.75l12.36,12.28c5.94-0.17,10.71-5.06,10.71-11.03c0-2.93-1.17-5.7-3.3-7.8
|
||||
c-2.11-2.09-4.86-3.24-7.74-3.24c-2.63,0-4.95,1.12-7.3,3.51C36.32,25.2,34.78,27.45,33.3,29.75z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M69.05,22.38v-9.47h0.66v9.47H69.05z"/>
|
||||
<path class="st2" d="M84.73,22.38h-0.67l-5.61-8.41H78.4c0.05,1,0.08,1.76,0.08,2.27v6.14h-0.63v-9.47h0.66l5.6,8.4h0.04
|
||||
c-0.04-0.78-0.06-1.52-0.06-2.22v-6.18h0.64V22.38z"/>
|
||||
<path class="st2" d="M93.51,22.38h-0.66v-9.47h5.19v0.61h-4.53v4.02h4.28v0.62h-4.28V22.38z"/>
|
||||
<path class="st2" d="M104.95,22.38v-9.47h0.66v9.47H104.95z"/>
|
||||
<path class="st2" d="M120.62,22.38h-0.67l-5.61-8.41h-0.05c0.05,1,0.08,1.76,0.08,2.27v6.14h-0.64v-9.47h0.66l5.6,8.4h0.04
|
||||
c-0.04-0.78-0.06-1.52-0.06-2.22v-6.18h0.64V22.38z"/>
|
||||
<path class="st2" d="M128.74,22.38v-9.47h0.66v9.47H128.74z"/>
|
||||
<path class="st2" d="M139.99,22.38h-0.67v-8.85h-3.08v-0.62h6.82v0.62h-3.08V22.38z"/>
|
||||
<path class="st2" d="M155.09,22.38h-5.18v-9.47h5.18v0.61h-4.52v3.58h4.26v0.61h-4.26v4.06h4.52V22.38z"/>
|
||||
<path class="st2" d="M79.58,25.23v8.17c0,0.93-0.21,1.75-0.63,2.45c-0.42,0.7-1.02,1.24-1.81,1.62s-1.72,0.56-2.8,0.56
|
||||
c-1.62,0-2.89-0.42-3.78-1.25s-1.35-1.97-1.35-3.42v-8.14h2.67v7.73c0,0.97,0.2,1.69,0.59,2.14s1.04,0.68,1.94,0.68
|
||||
c0.88,0,1.51-0.23,1.9-0.69s0.59-1.18,0.59-2.16v-7.71H79.58z"/>
|
||||
<path class="st2" d="M92.29,29.17c0,1.36-0.42,2.4-1.27,3.12c-0.85,0.72-2.06,1.08-3.62,1.08h-1.15v4.49h-2.68V25.23h4.03
|
||||
c1.53,0,2.7,0.33,3.49,0.99S92.29,27.86,92.29,29.17z M86.24,31.17h0.88c0.82,0,1.44-0.16,1.85-0.49c0.41-0.33,0.61-0.8,0.61-1.42
|
||||
c0-0.63-0.17-1.09-0.51-1.39s-0.88-0.45-1.61-0.45h-1.22V31.17z"/>
|
||||
<path class="st2" d="M95.55,37.86V25.23h2.68v10.42h5.12v2.21H95.55z"/>
|
||||
<path class="st2" d="M117.9,31.53c0,2.09-0.52,3.7-1.55,4.82c-1.04,1.12-2.52,1.68-4.46,1.68c-1.94,0-3.42-0.56-4.46-1.68
|
||||
s-1.55-2.74-1.55-4.84c0-2.1,0.52-3.71,1.56-4.82c1.04-1.11,2.53-1.66,4.47-1.66c1.94,0,3.43,0.56,4.45,1.68
|
||||
C117.38,27.82,117.9,29.43,117.9,31.53z M108.68,31.53c0,1.41,0.27,2.47,0.8,3.19c0.54,0.71,1.34,1.07,2.4,1.07
|
||||
c2.14,0,3.2-1.42,3.2-4.26c0-2.84-1.06-4.27-3.19-4.27c-1.07,0-1.87,0.36-2.41,1.08S108.68,30.12,108.68,31.53z"/>
|
||||
<path class="st2" d="M129.13,37.86l-0.92-3.01h-4.6l-0.92,3.01h-2.89l4.46-12.68h3.27l4.48,12.68H129.13z M127.58,32.61
|
||||
c-0.85-2.72-1.32-4.26-1.43-4.62c-0.11-0.36-0.18-0.64-0.23-0.85c-0.19,0.74-0.73,2.56-1.63,5.47H127.58z"/>
|
||||
<path class="st2" d="M144.97,31.42c0,2.08-0.59,3.67-1.78,4.78c-1.18,1.11-2.89,1.66-5.13,1.66h-3.58V25.23h3.96
|
||||
c2.06,0,3.66,0.54,4.8,1.63C144.4,27.95,144.97,29.47,144.97,31.42z M142.19,31.49c0-2.71-1.2-4.07-3.59-4.07h-1.43v8.22h1.15
|
||||
C140.9,35.65,142.19,34.26,142.19,31.49z"/>
|
||||
<path class="st2" d="M155.93,34.35c0,1.14-0.41,2.04-1.23,2.7c-0.82,0.66-1.96,0.99-3.42,0.99c-1.35,0-2.54-0.25-3.58-0.76v-2.49
|
||||
c0.85,0.38,1.57,0.65,2.16,0.8c0.59,0.16,1.13,0.23,1.62,0.23c0.59,0,1.04-0.11,1.35-0.34c0.31-0.22,0.47-0.56,0.47-1
|
||||
c0-0.25-0.07-0.47-0.21-0.66c-0.14-0.19-0.34-0.38-0.61-0.56c-0.27-0.18-0.81-0.46-1.64-0.86c-0.77-0.36-1.35-0.71-1.74-1.04
|
||||
s-0.69-0.72-0.92-1.17c-0.23-0.44-0.35-0.96-0.35-1.56c0-1.12,0.38-2,1.14-2.63c0.76-0.64,1.8-0.96,3.14-0.96
|
||||
c0.66,0,1.28,0.08,1.88,0.23c0.6,0.16,1.22,0.37,1.87,0.66L155,28.02c-0.67-0.28-1.23-0.47-1.67-0.58
|
||||
c-0.44-0.11-0.87-0.16-1.3-0.16c-0.51,0-0.9,0.12-1.17,0.35s-0.41,0.54-0.41,0.92c0,0.24,0.05,0.44,0.16,0.62s0.28,0.35,0.52,0.51
|
||||
c0.24,0.16,0.8,0.46,1.7,0.89c1.18,0.56,1.99,1.13,2.43,1.7C155.71,32.83,155.93,33.53,155.93,34.35z"/>
|
||||
</g>
|
||||
<image style="display:none;overflow:visible;" width="512" height="150" xlink:href="data:image/png;base64,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" transform="matrix(0.1096 0 0 0.1096 2.0607 1.7066)">
|
||||
</image>
|
||||
</svg>
|
Before Width: | Height: | Size: 34 KiB |
@ -1,17 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="19" viewBox="0 0 110 19">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f1f1f1;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #36b0e2;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15" y="7" width="78" height="6"/>
|
||||
<rect class="cls-1" x="8" y="7" width="50" height="6"/>
|
||||
<circle class="cls-1" cx="9.5" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="55" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="100.5" cy="9.5" r="9.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B |
@ -1,17 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="19" viewBox="0 0 110 19">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f1f1f1;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #36b0e2;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15" y="7" width="78" height="6"/>
|
||||
<rect class="cls-1" x="8" y="7" width="50" height="6"/>
|
||||
<circle class="cls-2" cx="9.5" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="55" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="100.5" cy="9.5" r="9.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B |
@ -1,17 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="19" viewBox="0 0 110 19">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f1f1f1;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #36b0e2;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15" y="7" width="78" height="6"/>
|
||||
<rect class="cls-2" x="8" y="7" width="50" height="6"/>
|
||||
<circle class="cls-2" cx="9.5" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="55" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="100.5" cy="9.5" r="9.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B |
@ -1,17 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="19" viewBox="0 0 110 19">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f1f1f1;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #36b0e2;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-1" x="15" y="7" width="78" height="6"/>
|
||||
<rect class="cls-2" x="8" y="7" width="50" height="6"/>
|
||||
<circle class="cls-2" cx="9.5" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-2" cx="55" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="100.5" cy="9.5" r="9.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B |
@ -1,17 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="19" viewBox="0 0 110 19">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f1f1f1;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #36b0e2;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" x="15" y="7" width="78" height="6"/>
|
||||
<rect class="cls-2" x="8" y="7" width="50" height="6"/>
|
||||
<circle class="cls-2" cx="9.5" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-2" cx="55" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-1" cx="100.5" cy="9.5" r="9.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B |
@ -1,17 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="19" viewBox="0 0 110 19">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f1f1f1;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #36b0e2;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" x="15" y="7" width="78" height="6"/>
|
||||
<rect class="cls-2" x="8" y="7" width="50" height="6"/>
|
||||
<circle class="cls-2" cx="9.5" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-2" cx="55" cy="9.5" r="9.5"/>
|
||||
<circle class="cls-2" cx="100.5" cy="9.5" r="9.5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B |
@ -1,7 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156.32 156.32">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#26a9e0;}.cls-2{fill:#4bc2ec;}</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M448.81,472.57a3.89,3.89,0,0,0,2.76,1.14,3.91,3.91,0,0,0,2.77-6.67l-23.45-23.45a3.92,3.92,0,0,0-5.53,0L401.91,467a3.91,3.91,0,0,0,5.53,5.53l16.78-16.78v84.36a3.91,3.91,0,1,0,7.81,0V455.79Z" transform="translate(-349.97 -387.74)"/>
|
||||
<path class="cls-2" d="M469.44,419.44a42.77,42.77,0,0,0-82.62,0A43,43,0,0,0,393,505H416.4v-7.82H393a35.17,35.17,0,0,1,0-70.34c.14,0,.26,0,.39,0a35,35,0,0,1,69.57,0c.13,0,.26,0,.39,0a35.17,35.17,0,0,1,0,70.34H439.85V505H463.3a43,43,0,0,0,6.14-85.54Z" transform="translate(-349.97 -387.74)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 739 B |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 16 KiB |
@ -1,12 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1190" height="300" viewBox="0 0 1190 300">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f1f1f1;
|
||||
fill-rule: evenodd;
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path id="Wave" class="cls-1" d="M195,523.781s131.407-192.134,319-37.772,450.273,94.3,545,38.489,326,17.429,326,17.429V831H195V523.781Z" transform="translate(-195 -426)"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 405 B |
@ -1,653 +0,0 @@
|
||||
jQuery(document).ready(function ($) {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.color-field').wpColorPicker();
|
||||
|
||||
var iupStopLoop = false;
|
||||
var iupProcessingLoop = false;
|
||||
var iupLoopErrors = 0;
|
||||
var iupAjaxCall = false;
|
||||
|
||||
//show a confirmation warning if leaving page during a bulk action
|
||||
$(window).on("unload", function () {
|
||||
if (iupProcessingLoop) {
|
||||
return iup_data.strings.leave_confirmation;
|
||||
}
|
||||
});
|
||||
|
||||
//show an error at top of main settings page
|
||||
var showError = function (error_message) {
|
||||
$('#iup-error').text(error_message.substr(0, 200)).show();
|
||||
$('html, body').animate({scrollTop: 0}, 1000);
|
||||
};
|
||||
|
||||
var buildFilelist = function (remaining_dirs, nonce = '') {
|
||||
if (iupStopLoop) {
|
||||
iupStopLoop = false;
|
||||
iupProcessingLoop = false;
|
||||
return false;
|
||||
}
|
||||
iupProcessingLoop = true;
|
||||
|
||||
var data = {remaining_dirs: remaining_dirs};
|
||||
if (nonce) {
|
||||
data.nonce = nonce;
|
||||
} else {
|
||||
data.nonce = iup_data.nonce.scan;
|
||||
}
|
||||
$.post(
|
||||
ajaxurl + '?action=infinite-uploads-filelist',
|
||||
data,
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
$('#iup-scan-storage').text(json.data.local_size);
|
||||
$('#iup-scan-files').text(json.data.local_files);
|
||||
$('#iup-scan-progress').show();
|
||||
if (!json.data.is_done) {
|
||||
buildFilelist(
|
||||
json.data.remaining_dirs,
|
||||
json.data.nonce
|
||||
);
|
||||
} else {
|
||||
iupProcessingLoop = false;
|
||||
location.reload();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
showError(json.data);
|
||||
$('.modal').modal('hide');
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('.modal').modal('hide');
|
||||
});
|
||||
};
|
||||
|
||||
var fetchRemoteFilelist = function (next_token, nonce = '') {
|
||||
if (iupStopLoop) {
|
||||
iupStopLoop = false;
|
||||
iupProcessingLoop = false;
|
||||
return false;
|
||||
}
|
||||
iupProcessingLoop = true;
|
||||
|
||||
var data = {next_token: next_token};
|
||||
if (nonce) {
|
||||
data.nonce = nonce;
|
||||
} else {
|
||||
data.nonce = iup_data.nonce.scan;
|
||||
}
|
||||
$.post(
|
||||
ajaxurl + '?action=infinite-uploads-remote-filelist',
|
||||
data,
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
$('#iup-scan-remote-storage').text(
|
||||
json.data.cloud_size
|
||||
);
|
||||
$('#iup-scan-remote-files').text(json.data.cloud_files);
|
||||
$('#iup-scan-remote-progress').show();
|
||||
if (!json.data.is_done) {
|
||||
fetchRemoteFilelist(
|
||||
json.data.next_token,
|
||||
json.data.nonce
|
||||
);
|
||||
} else {
|
||||
if ('upload' === window.iupNextStep) {
|
||||
//update values in next modal
|
||||
$('#iup-progress-size').text(
|
||||
json.data.remaining_size
|
||||
);
|
||||
$('#iup-progress-files').text(
|
||||
json.data.remaining_files
|
||||
);
|
||||
if ('0' == json.data.remaining_files) {
|
||||
$('#iup-upload-progress').hide();
|
||||
} else {
|
||||
$('#iup-upload-progress').show();
|
||||
}
|
||||
$('#iup-sync-progress-bar')
|
||||
.css('width', json.data.pcnt_complete + '%')
|
||||
.attr(
|
||||
'aria-valuenow',
|
||||
json.data.pcnt_complete
|
||||
)
|
||||
.text(json.data.pcnt_complete + '%');
|
||||
|
||||
$('#iup-sync-button').attr(
|
||||
'data-target',
|
||||
'#upload-modal'
|
||||
);
|
||||
$('.modal').modal('hide');
|
||||
$('#upload-modal').modal('show');
|
||||
} else if ('download' === window.iupNextStep) {
|
||||
$('.modal').modal('hide');
|
||||
$('#download-modal').modal('show');
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showError(json.data);
|
||||
$('.modal').modal('hide');
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('.modal').modal('hide');
|
||||
});
|
||||
};
|
||||
|
||||
var syncFilelist = function (nonce = '') {
|
||||
if (iupStopLoop) {
|
||||
iupStopLoop = false;
|
||||
iupProcessingLoop = false;
|
||||
return false;
|
||||
}
|
||||
iupProcessingLoop = true;
|
||||
|
||||
var data = {};
|
||||
if (nonce) {
|
||||
data.nonce = nonce;
|
||||
} else {
|
||||
data.nonce = iup_data.nonce.sync;
|
||||
}
|
||||
iupAjaxCall = $.post(
|
||||
ajaxurl + '?action=infinite-uploads-sync',
|
||||
data,
|
||||
function (json) {
|
||||
iupLoopErrors = 0;
|
||||
if (json.success) {
|
||||
//$('.iup-progress-pcnt').text(json.data.pcnt_complete);
|
||||
$('#iup-progress-size').text(json.data.remaining_size);
|
||||
$('#iup-progress-files').text(
|
||||
json.data.remaining_files
|
||||
);
|
||||
$('#iup-upload-progress').show();
|
||||
$('#iup-sync-progress-bar')
|
||||
.css('width', json.data.pcnt_complete + '%')
|
||||
.attr('aria-valuenow', json.data.pcnt_complete)
|
||||
.text(json.data.pcnt_complete + '%');
|
||||
if (!json.data.is_done) {
|
||||
data.nonce = json.data.nonce; //save for future errors
|
||||
syncFilelist(json.data.nonce);
|
||||
} else {
|
||||
iupStopLoop = true;
|
||||
$('#iup-upload-progress').hide();
|
||||
//update values in next modal
|
||||
$('#iup-enable-errors span').text(
|
||||
json.data.permanent_errors
|
||||
);
|
||||
if (json.data.permanent_errors) {
|
||||
$('.iup-enable-errors').show();
|
||||
}
|
||||
$('#iup-sync-button').attr(
|
||||
'data-target',
|
||||
'#enable-modal'
|
||||
);
|
||||
$('.modal').modal('hide');
|
||||
$('#enable-modal').modal('show');
|
||||
}
|
||||
if (
|
||||
Array.isArray(json.data.errors) &&
|
||||
json.data.errors.length
|
||||
) {
|
||||
$.each(json.data.errors, function (i, value) {
|
||||
$('#iup-sync-errors ul').append(
|
||||
'<li><span class="dashicons dashicons-warning"></span> ' +
|
||||
value +
|
||||
'</li>'
|
||||
);
|
||||
});
|
||||
$('#iup-sync-errors').show();
|
||||
var scroll = $('#iup-sync-errors')[0].scrollHeight;
|
||||
$('#iup-sync-errors').animate(
|
||||
{scrollTop: scroll},
|
||||
5000
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showError(json.data);
|
||||
$('.modal').modal('hide');
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
//if we get an error like 504 try up to 6 times with an exponential backoff to let the server cool down before giving up.
|
||||
iupLoopErrors++;
|
||||
if (iupLoopErrors > 6) {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('.modal').modal('hide');
|
||||
iupLoopErrors = 0;
|
||||
iupProcessingLoop = false;
|
||||
} else {
|
||||
var exponentialBackoff = Math.floor(
|
||||
Math.pow(iupLoopErrors, 2.5) * 1000
|
||||
); //max 90s
|
||||
console.log(
|
||||
'Server error. Waiting ' +
|
||||
exponentialBackoff +
|
||||
'ms before retrying'
|
||||
);
|
||||
setTimeout(function () {
|
||||
syncFilelist(data.nonce);
|
||||
}, exponentialBackoff);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var getSyncStatus = function () {
|
||||
if (!iupProcessingLoop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$.get(
|
||||
ajaxurl + '?action=infinite-uploads-status',
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
$('#iup-progress-size').text(json.data.remaining_size);
|
||||
$('#iup-progress-files').text(
|
||||
json.data.remaining_files
|
||||
);
|
||||
$('#iup-upload-progress').show();
|
||||
$('#iup-sync-progress-bar')
|
||||
.css('width', json.data.pcnt_complete + '%')
|
||||
.attr('aria-valuenow', json.data.pcnt_complete)
|
||||
.text(json.data.pcnt_complete + '%');
|
||||
} else {
|
||||
showError(json.data);
|
||||
}
|
||||
},
|
||||
'json'
|
||||
)
|
||||
.fail(function () {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
})
|
||||
.always(function () {
|
||||
setTimeout(function () {
|
||||
getSyncStatus();
|
||||
}, 15000);
|
||||
});
|
||||
};
|
||||
|
||||
var deleteFiles = function () {
|
||||
if (iupStopLoop) {
|
||||
iupStopLoop = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$.post(
|
||||
ajaxurl + '?action=infinite-uploads-delete',
|
||||
{nonce: iup_data.nonce.delete},
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
//$('.iup-progress-pcnt').text(json.data.pcnt_complete);
|
||||
$('#iup-delete-size').text(json.data.deletable_size);
|
||||
$('#iup-delete-files').text(json.data.deletable_files);
|
||||
if (!json.data.is_done) {
|
||||
deleteFiles();
|
||||
} else {
|
||||
location.reload();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
showError(json.data);
|
||||
$('.modal').modal('hide');
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('.modal').modal('hide');
|
||||
});
|
||||
};
|
||||
|
||||
var downloadFiles = function (nonce = '') {
|
||||
if (iupStopLoop) {
|
||||
iupStopLoop = false;
|
||||
iupProcessingLoop = false;
|
||||
return false;
|
||||
}
|
||||
iupProcessingLoop = true;
|
||||
|
||||
var data = {};
|
||||
if (nonce) {
|
||||
data.nonce = nonce;
|
||||
} else {
|
||||
data.nonce = iup_data.nonce.download;
|
||||
}
|
||||
$.post(
|
||||
ajaxurl + '?action=infinite-uploads-download',
|
||||
data,
|
||||
function (json) {
|
||||
iupLoopErrors = 0;
|
||||
if (json.success) {
|
||||
//$('.iup-progress-pcnt').text(json.data.pcnt_complete);
|
||||
$('#iup-download-size').text(json.data.deleted_size);
|
||||
$('#iup-download-files').text(json.data.deleted_files);
|
||||
$('#iup-download-progress').show();
|
||||
$('#iup-download-progress-bar')
|
||||
.css('width', json.data.pcnt_downloaded + '%')
|
||||
.attr('aria-valuenow', json.data.pcnt_downloaded)
|
||||
.text(json.data.pcnt_downloaded + '%');
|
||||
if (!json.data.is_done) {
|
||||
data.nonce = json.data.nonce; //save for future errors
|
||||
downloadFiles(json.data.nonce);
|
||||
} else {
|
||||
iupProcessingLoop = false;
|
||||
location.reload();
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
Array.isArray(json.data.errors) &&
|
||||
json.data.errors.length
|
||||
) {
|
||||
$.each(json.data.errors, function (i, value) {
|
||||
$('#iup-download-errors ul').append(
|
||||
'<li><span class="dashicons dashicons-warning"></span> ' +
|
||||
value +
|
||||
'</li>'
|
||||
);
|
||||
});
|
||||
$('#iup-download-errors').show();
|
||||
var scroll = $('#iup-download-errors')[0]
|
||||
.scrollHeight;
|
||||
$('#iup-download-errors').animate(
|
||||
{scrollTop: scroll},
|
||||
5000
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showError(json.data);
|
||||
$('.modal').modal('hide');
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
//if we get an error like 504 try up to 6 times before giving up.
|
||||
iupLoopErrors++;
|
||||
if (iupLoopErrors > 6) {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('.modal').modal('hide');
|
||||
iupLoopErrors = 0;
|
||||
iupProcessingLoop = false;
|
||||
} else {
|
||||
var exponentialBackoff = Math.floor(
|
||||
Math.pow(iupLoopErrors, 2.5) * 1000
|
||||
); //max 90s
|
||||
console.log(
|
||||
'Server error. Waiting ' +
|
||||
exponentialBackoff +
|
||||
'ms before retrying'
|
||||
);
|
||||
setTimeout(function () {
|
||||
downloadFiles(data.nonce);
|
||||
}, exponentialBackoff);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//Scan
|
||||
$('#scan-modal')
|
||||
.on('show.bs.modal', function () {
|
||||
$('#iup-error').hide();
|
||||
iupStopLoop = false;
|
||||
buildFilelist([]);
|
||||
})
|
||||
.on('hide.bs.modal', function () {
|
||||
iupStopLoop = true;
|
||||
iupProcessingLoop = false;
|
||||
});
|
||||
|
||||
//Compare to live
|
||||
$('#scan-remote-modal')
|
||||
.on('show.bs.modal', function (e) {
|
||||
$('#iup-error').hide();
|
||||
iupStopLoop = false;
|
||||
var button = $(e.relatedTarget); // Button that triggered the modal
|
||||
window.iupNextStep = button.data('next'); // Extract info from data-* attributes
|
||||
fetchRemoteFilelist(null);
|
||||
})
|
||||
.on('hide.bs.modal', function () {
|
||||
iupStopLoop = true;
|
||||
iupProcessingLoop = false;
|
||||
});
|
||||
|
||||
//Sync
|
||||
$('#upload-modal')
|
||||
.on('show.bs.modal', function () {
|
||||
$('.iup-enable-errors').hide(); //hide errors on enable modal
|
||||
$('#iup-collapse-errors').collapse('hide');
|
||||
$('#iup-error').hide();
|
||||
$('#iup-sync-errors').hide();
|
||||
$('#iup-sync-errors ul').empty();
|
||||
iupStopLoop = false;
|
||||
syncFilelist();
|
||||
setTimeout(function () {
|
||||
getSyncStatus();
|
||||
}, 15000);
|
||||
})
|
||||
.on('shown.bs.modal', function () {
|
||||
$('#scan-remote-modal').modal('hide');
|
||||
})
|
||||
.on('hide.bs.modal', function () {
|
||||
iupStopLoop = true;
|
||||
iupProcessingLoop = false;
|
||||
iupAjaxCall.abort();
|
||||
});
|
||||
|
||||
//Make sure upload modal closes
|
||||
$('#enable-modal')
|
||||
.on('shown.bs.modal', function () {
|
||||
$('#upload-modal').modal('hide');
|
||||
})
|
||||
.on('hidden.bs.modal', function () {
|
||||
$('#iup-enable-spinner').addClass('text-hide');
|
||||
$('#iup-enable-button').show();
|
||||
});
|
||||
|
||||
$('#iup-collapse-errors').on('show.bs.collapse', function () {
|
||||
// load up list of errors via ajax
|
||||
$.get(
|
||||
ajaxurl + '?action=infinite-uploads-sync-errors',
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
$('#iup-collapse-errors .list-group').html(json.data);
|
||||
}
|
||||
},
|
||||
'json'
|
||||
);
|
||||
});
|
||||
|
||||
$('#iup-resync-button').on('click', function (e) {
|
||||
$('.iup-enable-errors').hide(); //hide errors on enable modal
|
||||
$('#iup-collapse-errors').collapse('hide');
|
||||
$('#iup-enable-button').hide();
|
||||
$('#iup-enable-spinner').removeClass('text-hide');
|
||||
$.post(
|
||||
ajaxurl + '?action=infinite-uploads-reset-errors',
|
||||
{foo: 'bar'},
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
$('.modal').modal('hide');
|
||||
$('#upload-modal').modal('show');
|
||||
return true;
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('.modal').modal('hide');
|
||||
});
|
||||
});
|
||||
|
||||
//Download
|
||||
$('#download-modal')
|
||||
.on('show.bs.modal', function () {
|
||||
$('#iup-error').hide();
|
||||
$('#iup-download-errors').hide();
|
||||
$('#iup-download-errors ul').empty();
|
||||
iupStopLoop = false;
|
||||
downloadFiles();
|
||||
})
|
||||
.on('hide.bs.modal', function () {
|
||||
iupStopLoop = true;
|
||||
iupProcessingLoop = false;
|
||||
});
|
||||
|
||||
//Delete
|
||||
$('#delete-modal')
|
||||
.on('show.bs.modal', function () {
|
||||
$('#iup-error').hide();
|
||||
iupStopLoop = false;
|
||||
$('#iup-delete-local-button').show();
|
||||
$('#iup-delete-local-spinner').hide();
|
||||
})
|
||||
.on('hide.bs.modal', function () {
|
||||
iupStopLoop = true;
|
||||
});
|
||||
|
||||
//Delete local files
|
||||
$('#iup-delete-local-button').on('click', function () {
|
||||
$(this).hide();
|
||||
$('#iup-delete-local-spinner').show();
|
||||
deleteFiles();
|
||||
});
|
||||
|
||||
//Enable infinite uploads
|
||||
$('#iup-enable-button').on('click', function () {
|
||||
$('.iup-enable-errors').hide(); //hide errors on enable modal
|
||||
$('#iup-collapse-errors').collapse('hide');
|
||||
$('#iup-enable-button').hide();
|
||||
$('#iup-enable-spinner').removeClass('text-hide');
|
||||
$.post(
|
||||
ajaxurl + '?action=infinite-uploads-toggle',
|
||||
{enabled: true, nonce: iup_data.nonce.toggle},
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
location.reload();
|
||||
return true;
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('#iup-enable-spinner').addClass('text-hide');
|
||||
$('#iup-enable-button').show();
|
||||
$('.modal').modal('hide');
|
||||
});
|
||||
});
|
||||
|
||||
//Enable video cloud
|
||||
$('#iup-enable-video-button').on('click', function () {
|
||||
$('#iup-enable-video-button').hide();
|
||||
$('#iup-enable-video-spinner').removeClass('d-none').addClass('d-block');
|
||||
$.post(
|
||||
ajaxurl + '?action=infinite-uploads-video-activate',
|
||||
{nonce: iup_data.nonce.video},
|
||||
function (json) {
|
||||
if (json.success) {
|
||||
location.reload();
|
||||
return true;
|
||||
} else {
|
||||
$('#iup-enable-video-spinner').addClass('d-none').removeClass('d-block');
|
||||
$('#iup-enable-video-button').show();
|
||||
}
|
||||
},
|
||||
'json'
|
||||
).fail(function () {
|
||||
showError(iup_data.strings.ajax_error);
|
||||
$('#iup-enable-video-spinner').addClass('d-none').removeClass('d-block');
|
||||
$('#iup-enable-video-button').show();
|
||||
});
|
||||
});
|
||||
|
||||
//refresh api data
|
||||
$('.iup-refresh-icon .dashicons').on('click', function () {
|
||||
$(this).hide();
|
||||
$('.iup-refresh-icon .spinner-grow').removeClass('text-hide');
|
||||
window.location = $(this).attr('data-target');
|
||||
});
|
||||
|
||||
//Charts
|
||||
var bandwidthFormat = function (bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + ' B';
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return Math.round(bytes / 1024) + ' KB';
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return Math.round((bytes / 1024 / 1024) * 10) / 10 + ' MB';
|
||||
} else {
|
||||
return (
|
||||
Math.round((bytes / 1024 / 1024 / 1024) * 100) / 100 + ' GB'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var sizelabel = function (tooltipItem, data) {
|
||||
var label = ' ' + data.labels[tooltipItem.index] || '';
|
||||
return label;
|
||||
};
|
||||
|
||||
window.onload = function () {
|
||||
var pie1 = document.getElementById('iup-local-pie');
|
||||
if (pie1) {
|
||||
var config_local = {
|
||||
type: 'pie',
|
||||
data: iup_data.local_types,
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: false,
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: sizelabel,
|
||||
},
|
||||
backgroundColor: '#F1F1F1',
|
||||
bodyFontColor: '#2A2A2A',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
fontSize: 18,
|
||||
fontStyle: 'normal',
|
||||
text: iup_data.local_types.total,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var ctx = pie1.getContext('2d');
|
||||
window.myPieLocal = new Chart(ctx, config_local);
|
||||
}
|
||||
|
||||
var pie2 = document.getElementById('iup-cloud-pie');
|
||||
if (pie2) {
|
||||
var config_cloud = {
|
||||
type: 'pie',
|
||||
data: iup_data.cloud_types,
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: false,
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: sizelabel,
|
||||
},
|
||||
backgroundColor: '#F1F1F1',
|
||||
bodyFontColor: '#2A2A2A',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
fontSize: 18,
|
||||
fontStyle: 'normal',
|
||||
text: iup_data.cloud_types.total,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var ctx = pie2.getContext('2d');
|
||||
window.myPieCloud = new Chart(ctx, config_cloud);
|
||||
}
|
||||
};
|
||||
});
|
@ -1,982 +0,0 @@
|
||||
<?php
|
||||
|
||||
use UglyRobot\Infinite_Uploads\Aws\S3\Transfer;
|
||||
use UglyRobot\Infinite_Uploads\Aws\Middleware;
|
||||
use UglyRobot\Infinite_Uploads\Aws\ResultInterface;
|
||||
use UglyRobot\Infinite_Uploads\Aws\CommandPool;
|
||||
use UglyRobot\Infinite_Uploads\Aws\Exception\AwsException;
|
||||
use UglyRobot\Infinite_Uploads\Aws\Exception\S3Exception;
|
||||
use UglyRobot\Infinite_Uploads\Aws\S3\MultipartUploader;
|
||||
use UglyRobot\Infinite_Uploads\Aws\Exception\MultipartUploadException;
|
||||
|
||||
class Infinite_Uploads_Admin {
|
||||
|
||||
private static $instance;
|
||||
public $ajax_timelimit = 20;
|
||||
private $iup_instance;
|
||||
private $api;
|
||||
private $video;
|
||||
private $auth_error;
|
||||
|
||||
public function __construct() {
|
||||
$this->iup_instance = Infinite_Uploads::get_instance();
|
||||
$this->api = Infinite_Uploads_Api_Handler::get_instance();
|
||||
$this->video = Infinite_Uploads_Video::get_instance();
|
||||
|
||||
if ( is_multisite() ) {
|
||||
//multisite
|
||||
add_action( 'network_admin_menu', [ &$this, 'admin_menu' ] );
|
||||
//add_action( 'load-settings_page_infinite_uploads', [ &$this, 'intercept_auth' ] );
|
||||
add_action( 'load-toplevel_page_infinite_uploads', [ &$this, 'intercept_auth' ] );
|
||||
add_filter( 'network_admin_plugin_action_links_infinite-uploads/infinite-uploads.php', [ &$this, 'plugins_list_links' ] );
|
||||
} else {
|
||||
//single site
|
||||
add_action( 'admin_menu', [ &$this, 'admin_menu' ] );
|
||||
add_action( 'load-toplevel_page_infinite_uploads', [ &$this, 'intercept_auth' ] );
|
||||
add_filter( 'plugin_action_links_infinite-uploads/infinite-uploads.php', [ &$this, 'plugins_list_links' ] );
|
||||
}
|
||||
|
||||
add_action( 'admin_init', [ &$this, 'privacy_policy' ] );
|
||||
add_action( 'deactivate_plugin', [ &$this, 'block_bulk_deactivate' ] );
|
||||
|
||||
if ( is_main_site() ) {
|
||||
add_action( 'wp_ajax_infinite-uploads-filelist', [ &$this, 'ajax_filelist' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-remote-filelist', [ &$this, 'ajax_remote_filelist' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-sync', [ &$this, 'ajax_sync' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-sync-errors', [ &$this, 'ajax_sync_errors' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-reset-errors', [ &$this, 'ajax_reset_errors' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-delete', [ &$this, 'ajax_delete' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-download', [ &$this, 'ajax_download' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-toggle', [ &$this, 'ajax_toggle' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-status', [ &$this, 'ajax_status' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Infinite_Uploads_Admin
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new Infinite_Uploads_Admin();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a privacy policy statement.
|
||||
*/
|
||||
function privacy_policy() {
|
||||
if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
|
||||
return;
|
||||
}
|
||||
$content = '<p>'
|
||||
. sprintf(
|
||||
esc_html__( 'When you upload files on this site, your files are transferred to and stored in the Infinite Uploads cloud. When you visit pages on this site media files may be downloaded from the Infinite Uploads cloud CDN which stores web log information including IP, User Agent, referrer, Location, and ISP info of site visitors for 7 days. The Infinite Uploads privacy policy is %1$s here %2$s.', 'infinite-uploads' ),
|
||||
'<a href="https://infiniteuploads.com/privacy/?utm_source=iup_plugin&utm_medium=privacy_policy&utm_campaign=iup_plugin" target="_blank">', '</a>'
|
||||
) . '</p>';
|
||||
wp_add_privacy_policy_content( esc_html__( 'Infinite Uploads', 'infinite-uploads' ), wp_kses_post( wpautop( $content, false ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a debugging line.
|
||||
*/
|
||||
function sync_debug_log( $message ) {
|
||||
if ( defined( 'INFINITE_UPLOADS_API_DEBUG' ) && INFINITE_UPLOADS_API_DEBUG ) {
|
||||
$log = '[INFINITE_UPLOADS Sync Debug] %s %s';
|
||||
|
||||
$msg = sprintf(
|
||||
$log,
|
||||
INFINITE_UPLOADS_VERSION,
|
||||
$message
|
||||
);
|
||||
error_log( $msg );
|
||||
}
|
||||
}
|
||||
|
||||
public function ajax_status() {
|
||||
// check caps
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
wp_send_json_success( $this->iup_instance->get_sync_stats() );
|
||||
}
|
||||
|
||||
public function ajax_sync_errors() {
|
||||
global $wpdb;
|
||||
|
||||
// check caps
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$html = '';
|
||||
$error_list = $wpdb->get_results( "SELECT file, size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors >= 3" );
|
||||
foreach ( $error_list as $error ) {
|
||||
$html .= sprintf( '<li class="list-group-item list-group-item-warning">%s - %s</li>', esc_html( $error->file ), size_format( $error->size, 2 ) ) . PHP_EOL;
|
||||
}
|
||||
wp_send_json_success( $html );
|
||||
}
|
||||
|
||||
public function ajax_reset_errors() {
|
||||
global $wpdb;
|
||||
|
||||
// check caps
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$result = $wpdb->query( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET errors = 0, transferred = 0 WHERE synced = 0 AND errors >= 3" );
|
||||
|
||||
wp_send_json_success( $result );
|
||||
}
|
||||
|
||||
|
||||
public function ajax_filelist() {
|
||||
global $wpdb;
|
||||
|
||||
// check caps
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) || ! wp_verify_nonce( $_POST['nonce'], 'iup_scan' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$this->sync_debug_log( "Ajax time limit: " . $this->ajax_timelimit );
|
||||
|
||||
$path = $this->iup_instance->get_original_upload_dir_root();
|
||||
$path = $path['basedir'];
|
||||
|
||||
$remaining_dirs = [];
|
||||
//validate path is within uploads dir to prevent path traversal
|
||||
if ( isset( $_POST['remaining_dirs'] ) && is_array( $_POST['remaining_dirs'] ) ) {
|
||||
foreach ( $_POST['remaining_dirs'] as $dir ) {
|
||||
$realpath = realpath( $path . $dir );
|
||||
if ( 0 === strpos( $realpath, $path ) ) { //check that parsed path begins with upload dir
|
||||
$remaining_dirs[] = $dir;
|
||||
}
|
||||
}
|
||||
} elseif ( ! empty( $this->iup_instance->bucket ) ) {
|
||||
//If we are starting a new filesync and are logged into cloud storage abort any unfinished multipart uploads
|
||||
$to_abort = $wpdb->get_results( "SELECT file, transfer_status as upload_id FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE transfer_status IS NOT NULL" );
|
||||
if ( $to_abort ) {
|
||||
$s3 = $this->iup_instance->s3();
|
||||
$prefix = $this->iup_instance->get_s3_prefix();
|
||||
$bucket = $this->iup_instance->get_s3_bucket();
|
||||
$commands = [];
|
||||
foreach ( $to_abort as $file ) {
|
||||
$key = $prefix . $file->file;
|
||||
// Abort the multipart upload.
|
||||
$commands[] = $s3->getCommand( 'abortMultipartUpload', [
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $key,
|
||||
'UploadId' => $file->upload_id,
|
||||
] );
|
||||
$this->sync_debug_log( "Aborting multipart upload for {$file->file} UploadId {$file->upload_id}" );
|
||||
}
|
||||
// Create a command pool
|
||||
$pool = new CommandPool( $s3, $commands );
|
||||
|
||||
// Begin asynchronous execution of the commands
|
||||
$promise = $pool->promise();
|
||||
}
|
||||
}
|
||||
|
||||
$filelist = new Infinite_Uploads_Filelist( $path, $this->ajax_timelimit, $remaining_dirs );
|
||||
$filelist->start();
|
||||
$this_file_count = count( $filelist->file_list );
|
||||
$remaining_dirs = $filelist->paths_left;
|
||||
$is_done = $filelist->is_done;
|
||||
$nonce = wp_create_nonce( 'iup_scan' );
|
||||
|
||||
$data = compact( 'this_file_count', 'is_done', 'remaining_dirs', 'nonce' );
|
||||
$stats = $this->iup_instance->get_sync_stats();
|
||||
if ( $stats ) {
|
||||
$data = array_merge( $data, $stats );
|
||||
}
|
||||
|
||||
// Force the abortMultipartUpload pool to complete synchronously just in case it hasn't finished
|
||||
if ( isset( $promise ) ) {
|
||||
$promise->wait();
|
||||
}
|
||||
|
||||
wp_send_json_success( $data );
|
||||
}
|
||||
|
||||
public function ajax_remote_filelist() {
|
||||
global $wpdb;
|
||||
|
||||
// check caps
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) || ! wp_verify_nonce( $_POST['nonce'], 'iup_scan' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$this->sync_debug_log( "Ajax time limit: " . $this->ajax_timelimit );
|
||||
|
||||
$s3 = $this->iup_instance->s3();
|
||||
$prefix = $this->iup_instance->get_s3_prefix();
|
||||
|
||||
$args = [
|
||||
'Bucket' => $this->iup_instance->get_s3_bucket(),
|
||||
'Prefix' => trailingslashit( $prefix ),
|
||||
];
|
||||
|
||||
if ( ! empty( $_POST['next_token'] ) ) {
|
||||
$args['ContinuationToken'] = sanitize_text_field( $_POST['next_token'] );
|
||||
} else {
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['compare_started'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
|
||||
try {
|
||||
$results = $s3->getPaginator( 'ListObjectsV2', $args );
|
||||
$req_count = $file_count = 0;
|
||||
$is_done = false;
|
||||
$next_token = null;
|
||||
foreach ( $results as $result ) {
|
||||
$req_count ++;
|
||||
$is_done = ! $result['IsTruncated'];
|
||||
$next_token = isset( $result['NextContinuationToken'] ) ? $result['NextContinuationToken'] : null;
|
||||
$cloud_only_files = [];
|
||||
if ( $result['Contents'] ) {
|
||||
foreach ( $result['Contents'] as $object ) {
|
||||
$file_count ++;
|
||||
$local_key = str_replace( $prefix, '', $object['Key'] );
|
||||
$file = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->base_prefix}infinite_uploads_files WHERE file = %s", $local_key ) );
|
||||
if ( $file && ! $file->synced && $file->size == $object['Size'] ) {
|
||||
$this->sync_debug_log( "Already synced file found: $local_key " . size_format( $file->size, 2 ) );
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'synced' => 1, 'transferred' => $file->size ], [ 'file' => $local_key ] );
|
||||
}
|
||||
if ( ! $file ) {
|
||||
$this->sync_debug_log( "Cloud only file found: $local_key " . size_format( $object['Size'], 2 ) );
|
||||
$cloud_only_files[] = [
|
||||
'name' => $local_key,
|
||||
'size' => $object['Size'],
|
||||
'mtime' => strtotime( $object['LastModified']->__toString() ),
|
||||
'type' => $this->iup_instance->get_file_type( $local_key ),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//flush new files to db
|
||||
if ( count( $cloud_only_files ) ) {
|
||||
$values = [];
|
||||
foreach ( $cloud_only_files as $file ) {
|
||||
$values[] = $wpdb->prepare( "(%s,%d,%d,%s,%d,1,1)", $file['name'], $file['size'], $file['mtime'], $file['type'], $file['size'] );
|
||||
}
|
||||
|
||||
$query = "INSERT INTO {$wpdb->base_prefix}infinite_uploads_files (file, size, modified, type, transferred, synced, deleted) VALUES ";
|
||||
$query .= implode( ",\n", $values );
|
||||
$query .= " ON DUPLICATE KEY UPDATE size = VALUES(size), modified = VALUES(modified), type = VALUES(type), transferred = VALUES(transferred), synced = 1, deleted = 1, errors = 0";
|
||||
$wpdb->query( $query );
|
||||
}
|
||||
|
||||
if ( ( $timer = timer_stop() ) >= $this->ajax_timelimit ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $is_done ) {
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['compare_finished'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
|
||||
|
||||
$nonce = wp_create_nonce( 'iup_scan' );
|
||||
$data = compact( 'file_count', 'req_count', 'is_done', 'next_token', 'timer', 'nonce' );
|
||||
$stats = $this->iup_instance->get_sync_stats();
|
||||
if ( $stats ) {
|
||||
$data = array_merge( $data, $stats );
|
||||
}
|
||||
|
||||
wp_send_json_success( $data );
|
||||
} catch ( Exception $e ) {
|
||||
wp_send_json_error( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public function ajax_sync() {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) || ! wp_verify_nonce( $_POST['nonce'], 'iup_sync' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
if ( ! $progress['sync_started'] ) {
|
||||
$progress['sync_started'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
|
||||
//this loop has a parallel status check, so we make the timeout 2/3 of max execution time.
|
||||
$this->ajax_timelimit = max( 20, floor( ini_get( 'max_execution_time' ) * .6666 ) );
|
||||
$this->sync_debug_log( "Ajax time limit: " . $this->ajax_timelimit );
|
||||
$uploaded = 0;
|
||||
$errors = [];
|
||||
$break = false;
|
||||
$is_done = false;
|
||||
$path = $this->iup_instance->get_original_upload_dir_root();
|
||||
$s3 = $this->iup_instance->s3();
|
||||
while ( ! $break ) {
|
||||
$to_sync = $wpdb->get_results( $wpdb->prepare( "SELECT file, size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors < 3 AND transfer_status IS NULL ORDER BY errors ASC, file ASC LIMIT %d", INFINITE_UPLOADS_SYNC_PER_LOOP ) );
|
||||
if ( $to_sync ) {
|
||||
//build full paths
|
||||
$to_sync_full = [];
|
||||
$to_sync_size = 0;
|
||||
$to_sync_sql = [];
|
||||
foreach ( $to_sync as $file ) {
|
||||
$to_sync_size += $file->size;
|
||||
if ( count( $to_sync_full ) && $to_sync_size > INFINITE_UPLOADS_SYNC_MAX_BYTES ) { //upload at minimum one file even if it's huuuge
|
||||
break;
|
||||
}
|
||||
$to_sync_full[] = $path['basedir'] . $file->file;
|
||||
$to_sync_sql[] = esc_sql( $file->file );
|
||||
}
|
||||
//preset the error count in case request times out. Successful sync will clear error count.
|
||||
$wpdb->query( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET errors = ( errors + 1 ) WHERE file IN ('" . implode( "','", $to_sync_sql ) . "')" );
|
||||
|
||||
$this->sync_debug_log( "Transfer manager batch size " . size_format( $to_sync_size, 2 ) . ", " . count( $to_sync_full ) . " files." );
|
||||
$concurrency = count( $to_sync_full ) > 1 ? INFINITE_UPLOADS_SYNC_CONCURRENCY : INFINITE_UPLOADS_SYNC_MULTIPART_CONCURRENCY;
|
||||
$obj = new ArrayObject( $to_sync_full );
|
||||
$from = $obj->getIterator();
|
||||
|
||||
$transfer_args = [
|
||||
'concurrency' => $concurrency,
|
||||
'base_dir' => $path['basedir'],
|
||||
'before' => function ( UglyRobot\Infinite_Uploads\Aws\Command $command ) use ( $wpdb, &$uploaded, &$errors, &$part_sizes ) {
|
||||
//add middleware to modify object headers
|
||||
if ( in_array( $command->getName(), [ 'PutObject', 'CreateMultipartUpload' ], true ) ) {
|
||||
/// Expires:
|
||||
if ( defined( 'INFINITE_UPLOADS_HTTP_EXPIRES' ) ) {
|
||||
$command['Expires'] = INFINITE_UPLOADS_HTTP_EXPIRES;
|
||||
}
|
||||
// Cache-Control:
|
||||
if ( defined( 'INFINITE_UPLOADS_HTTP_CACHE_CONTROL' ) ) {
|
||||
if ( is_numeric( INFINITE_UPLOADS_HTTP_CACHE_CONTROL ) ) {
|
||||
$command['CacheControl'] = 'max-age=' . INFINITE_UPLOADS_HTTP_CACHE_CONTROL;
|
||||
} else {
|
||||
$command['CacheControl'] = INFINITE_UPLOADS_HTTP_CACHE_CONTROL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( in_array( $command->getName(), [ 'PutObject' ], true ) ) {
|
||||
$this->sync_debug_log( "Uploading key {$command['Key']}" );
|
||||
}
|
||||
|
||||
//add middleware to intercept result of each file upload
|
||||
if ( in_array( $command->getName(), [ 'PutObject', 'CompleteMultipartUpload' ], true ) ) {
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $wpdb, &$uploaded, $command ) {
|
||||
$this->sync_debug_log( "Finished uploading file: " . $command['Key'] );
|
||||
$uploaded ++;
|
||||
$file = $this->iup_instance->get_file_from_result( $result );
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET transferred = size, synced = 1, errors = 0, transfer_status = null WHERE file = %s", $file ) );
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
//add middleware to intercept result and record the uploadId for resuming later
|
||||
if ( in_array( $command->getName(), [ 'CreateMultipartUpload' ], true ) ) {
|
||||
$this->sync_debug_log( "Starting multipart upload for key {$command['Key']}" );
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $wpdb ) {
|
||||
$file = $this->iup_instance->get_file_from_result( $result );
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'synced' => 0, 'transfer_status' => $result['UploadId'] ], [ 'file' => $file ], [ '%d', '%s' ] );
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
//add middleware to check if we should bail before each new upload part
|
||||
if ( in_array( $command->getName(), [ 'UploadPart' ], true ) ) {
|
||||
$this->sync_debug_log( "Uploading key {$command['Key']} part {$command['PartNumber']}" );
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $command ) {
|
||||
global $wpdb;
|
||||
$this->sync_debug_log( "Finished Uploading key {$command['Key']} part {$command['PartNumber']}" );
|
||||
|
||||
$file = $this->iup_instance->get_file_from_result( $result );
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET transferred = ( transferred + %d ), synced = 0, errors = 0 WHERE file = %s", $command['ContentLength'], $file ) );
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
try {
|
||||
$manager = new Transfer( $s3, $from, 's3://' . $this->iup_instance->bucket . '/', $transfer_args );
|
||||
$manager->transfer();
|
||||
} catch ( Exception $e ) {
|
||||
$this->sync_debug_log( "Transfer sync exception: " . $e->__toString() );
|
||||
if ( method_exists( $e, 'getRequest' ) ) {
|
||||
$file = str_replace( trailingslashit( $this->iup_instance->bucket ), '', $e->getRequest()->getRequestTarget() );
|
||||
$error_count = $wpdb->get_var( $wpdb->prepare( "SELECT errors FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE file = %s", $file ) );
|
||||
if ( $error_count >= 3 ) {
|
||||
$errors[] = sprintf( esc_html__( 'Error uploading %s. Retries exceeded.', 'infinite-uploads' ), $file );
|
||||
} else {
|
||||
$errors[] = sprintf( esc_html__( 'Error uploading %s. Queued for retry.', 'infinite-uploads' ), $file );
|
||||
}
|
||||
} else { //I don't know which error case trigger this but it's common
|
||||
$errors[] = esc_html__( 'Error uploading file. Queued for retry.', 'infinite-uploads' );
|
||||
}
|
||||
}
|
||||
|
||||
} else { // we are done with transfer manager, continue any unfinished multipart uploads one by one
|
||||
|
||||
$to_sync = $wpdb->get_row( "SELECT file, size, errors, transfer_status as upload_id FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors < 3 AND transfer_status IS NOT NULL ORDER BY errors ASC, file ASC LIMIT 1" );
|
||||
if ( $to_sync ) {
|
||||
$this->sync_debug_log( "Continuing multipart upload: " . $to_sync->file );
|
||||
|
||||
//preset the error count in case request times out. Successful sync will clear error count.
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET errors = ( errors + 1 ) WHERE file = %s", $to_sync->file ) );
|
||||
$to_sync->errors ++; //increment error result so it's accurate
|
||||
|
||||
$key = $this->iup_instance->get_s3_prefix() . $to_sync->file;
|
||||
try {
|
||||
$upload_state = $this->iup_instance->get_multipart_upload_state( $key, $to_sync->upload_id );
|
||||
$progress = round( ( ( count( $upload_state->getUploadedParts() ) * $upload_state->getPartSize() ) / $to_sync->size ) * 100 );
|
||||
$this->sync_debug_log( sprintf( 'Uploaded %s%% of file (%d, %s parts)', $progress, count( $upload_state->getUploadedParts() ), size_format( $upload_state->getPartSize() ) ) );
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'transferred' => ( count( $upload_state->getUploadedParts() ) * $upload_state->getPartSize() ) ], [ 'file' => $to_sync->file ], [ '%d' ] );
|
||||
|
||||
$parts_started = [];
|
||||
$source = $path['basedir'] . $to_sync->file;
|
||||
$uploader = new MultipartUploader( $s3, $source, [
|
||||
'concurrency' => INFINITE_UPLOADS_SYNC_MULTIPART_CONCURRENCY,
|
||||
'state' => $upload_state,
|
||||
'before_upload' => function ( UglyRobot\Infinite_Uploads\Aws\Command $command ) use ( &$parts_started, $uploaded, $errors ) {
|
||||
|
||||
$this->sync_debug_log( "Uploading key {$command['Key']} part {$command['PartNumber']}" );
|
||||
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $command, &$parts_started, $uploaded, $errors ) {
|
||||
global $wpdb;
|
||||
$this->sync_debug_log( "Finished Uploading key {$command['Key']} part {$command['PartNumber']}" );
|
||||
|
||||
$file = $this->iup_instance->get_file_from_result( $result );
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET transferred = ( transferred + %d ), synced = 0, errors = 0 WHERE file = %s", $command['ContentLength'], $file ) );
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
},
|
||||
] );
|
||||
|
||||
//Recover from errors
|
||||
do {
|
||||
try {
|
||||
$result = $uploader->upload();
|
||||
} catch ( MultipartUploadException $e ) {
|
||||
$uploader = new MultipartUploader( $s3, $source, [
|
||||
'state' => $e->getState(),
|
||||
'before_upload' => function ( UglyRobot\Infinite_Uploads\Aws\Command $command ) use ( $wpdb ) {
|
||||
$this->sync_debug_log( "Uploading key {$command['Key']} part {$command['PartNumber']}" );
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $wpdb, $command ) {
|
||||
global $wpdb;
|
||||
$this->sync_debug_log( "Finished Uploading key {$command['Key']} part {$command['PartNumber']}" );
|
||||
|
||||
$file = $this->iup_instance->get_file_from_result( $result );
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET transferred = ( transferred + %d ), synced = 0, errors = 0 WHERE file = %s", $command['ContentLength'], $file ) );
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
},
|
||||
] );
|
||||
}
|
||||
} while ( ! isset( $result ) );
|
||||
|
||||
//Abort a multipart upload if failed a second time
|
||||
try {
|
||||
$result = $uploader->upload();
|
||||
$this->sync_debug_log( "Finished multipart file upload: " . $to_sync->file );
|
||||
$uploaded ++;
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'transferred' => $to_sync->size, 'synced' => 1, 'errors' => 0, 'transfer_status' => null ], [ 'file' => $to_sync->file ], [ '%d', '%d', '%d', null ] );
|
||||
} catch ( MultipartUploadException $e ) {
|
||||
$params = $e->getState()->getId();
|
||||
$result = $s3->abortMultipartUpload( $params );
|
||||
//restart the multipart
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'transferred' => 0, 'synced' => 0, 'transfer_status' => null ], [ 'file' => $to_sync->file ], [ '%d', null ] );
|
||||
$this->sync_debug_log( "Get multipart retry UploadState exception: " . $e->__toString() );
|
||||
if ( ( $to_sync->errors ) >= 3 ) {
|
||||
$errors[] = sprintf( esc_html__( 'Error uploading %s. Retries exceeded.', 'infinite-uploads' ), $to_sync->file );
|
||||
} else {
|
||||
$errors[] = sprintf( esc_html__( 'Error uploading %s. Queued for retry.', 'infinite-uploads' ), $to_sync->file );
|
||||
}
|
||||
}
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
$this->sync_debug_log( "Get multipart UploadState exception: " . $e->__toString() );
|
||||
if ( ( $to_sync->errors ) >= 3 ) {
|
||||
$errors[] = sprintf( esc_html__( 'Error uploading %s. Retries exceeded.', 'infinite-uploads' ), $to_sync->file );
|
||||
} else {
|
||||
$errors[] = sprintf( esc_html__( 'Error uploading %s. Queued for retry.', 'infinite-uploads' ), $to_sync->file );
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$is_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $is_done || timer_stop() >= $this->ajax_timelimit ) {
|
||||
$break = true;
|
||||
$permanent_errors = false;
|
||||
|
||||
if ( $is_done ) {
|
||||
$permanent_errors = (int) $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors >= 3" );
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['sync_finished'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
|
||||
$nonce = wp_create_nonce( 'iup_sync' );
|
||||
wp_send_json_success( array_merge( compact( 'uploaded', 'is_done', 'errors', 'permanent_errors', 'nonce' ), $this->iup_instance->get_sync_stats() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function ajax_delete() {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) || ! wp_verify_nonce( $_POST['nonce'], 'iup_delete' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$deleted = 0;
|
||||
$errors = [];
|
||||
$path = $this->iup_instance->get_original_upload_dir_root();
|
||||
$break = false;
|
||||
while ( ! $break ) {
|
||||
$to_delete = $wpdb->get_col( "SELECT file FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 0 LIMIT 500" );
|
||||
foreach ( $to_delete as $file ) {
|
||||
@unlink( $path['basedir'] . $file );
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'deleted' => 1 ], [ 'file' => $file ] );
|
||||
$deleted ++;
|
||||
}
|
||||
|
||||
$is_done = ! (bool) $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 0" );
|
||||
if ( $is_done || timer_stop() >= $this->ajax_timelimit ) {
|
||||
$break = true;
|
||||
wp_send_json_success( array_merge( compact( 'deleted', 'is_done', 'errors' ), $this->iup_instance->get_sync_stats() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function ajax_download() {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) || ! wp_verify_nonce( $_POST['nonce'], 'iup_download' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
if ( empty( $progress['download_started'] ) ) {
|
||||
$progress['download_started'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
|
||||
$downloaded = 0;
|
||||
$errors = [];
|
||||
$break = false;
|
||||
$path = $this->iup_instance->get_original_upload_dir_root();
|
||||
$s3 = $this->iup_instance->s3();
|
||||
while ( ! $break ) {
|
||||
$to_sync = $wpdb->get_results( $wpdb->prepare( "SELECT file, size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 1 AND errors < 3 ORDER BY errors ASC, file ASC LIMIT %d", INFINITE_UPLOADS_SYNC_PER_LOOP ) );
|
||||
//build full paths
|
||||
$to_sync_full = [];
|
||||
$to_sync_size = 0;
|
||||
$to_sync_sql = [];
|
||||
foreach ( $to_sync as $file ) {
|
||||
$to_sync_size += $file->size;
|
||||
if ( count( $to_sync_full ) && $to_sync_size > INFINITE_UPLOADS_SYNC_MAX_BYTES ) { //upload at minimum one file even if it's huuuge
|
||||
break;
|
||||
}
|
||||
$to_sync_full[] = 's3://' . untrailingslashit( $this->iup_instance->bucket ) . $file->file;
|
||||
$to_sync_sql[] = esc_sql( $file->file );
|
||||
}
|
||||
//preset the error count in case request times out. Successful sync will clear error count.
|
||||
$wpdb->query( "UPDATE `{$wpdb->base_prefix}infinite_uploads_files` SET errors = ( errors + 1 ) WHERE file IN ('" . implode( "','", $to_sync_sql ) . "')" );
|
||||
|
||||
$obj = new ArrayObject( $to_sync_full );
|
||||
$from = $obj->getIterator();
|
||||
|
||||
$transfer_args = [
|
||||
'concurrency' => INFINITE_UPLOADS_SYNC_CONCURRENCY,
|
||||
'base_dir' => 's3://' . $this->iup_instance->bucket,
|
||||
'before' => function ( UglyRobot\Infinite_Uploads\Aws\Command $command ) use ( $wpdb, &$downloaded ) {//add middleware to intercept result of each file upload
|
||||
if ( in_array( $command->getName(), [ 'GetObject' ], true ) ) {
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $wpdb, &$downloaded ) {
|
||||
$downloaded ++;
|
||||
$file = $this->iup_instance->get_file_from_result( $result );
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'deleted' => 0, 'errors' => 0 ], [ 'file' => $file ] );
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
try {
|
||||
$manager = new Transfer( $s3, $from, $path['basedir'], $transfer_args );
|
||||
$manager->transfer();
|
||||
} catch ( Exception $e ) {
|
||||
if ( method_exists( $e, 'getRequest' ) ) {
|
||||
$file = str_replace( untrailingslashit( $path['basedir'] ), '', str_replace( trailingslashit( $this->iup_instance->bucket ), '', $e->getRequest()->getRequestTarget() ) );
|
||||
$error_count = $wpdb->get_var( $wpdb->prepare( "SELECT errors FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE file = %s", $file ) );
|
||||
if ( $error_count >= 3 ) {
|
||||
$errors[] = sprintf( esc_html__( 'Error downloading %s. Retries exceeded.', 'infinite-uploads' ), $file );
|
||||
} else {
|
||||
$errors[] = sprintf( esc_html__( 'Error downloading %s. Queued for retry.', 'infinite-uploads' ), $file );
|
||||
}
|
||||
} else {
|
||||
$errors[] = esc_html__( 'Error downloading file. Queued for retry.', 'infinite-uploads' );
|
||||
}
|
||||
}
|
||||
|
||||
$is_done = ! (bool) $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 1 AND errors < 3" );
|
||||
if ( $is_done || timer_stop() >= $this->ajax_timelimit ) {
|
||||
$break = true;
|
||||
|
||||
if ( $is_done ) {
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['download_finished'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
|
||||
$this->api->disconnect();
|
||||
}
|
||||
|
||||
$nonce = wp_create_nonce( 'iup_download' );
|
||||
wp_send_json_success( array_merge( compact( 'downloaded', 'is_done', 'errors', 'nonce' ), $this->iup_instance->get_sync_stats() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable url rewriting
|
||||
*/
|
||||
public function ajax_toggle() {
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) || ! wp_verify_nonce( $_POST['nonce'], 'iup_toggle' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
$enabled = (bool) $_REQUEST['enabled'];
|
||||
$this->iup_instance->toggle_cloud( $enabled );
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to WP core size_format() function except it returns "0 GB" instead of false on failure.
|
||||
*
|
||||
* @param int|string $bytes Number of bytes. Note max integer size for integers.
|
||||
* @param int $decimals Optional. Precision of number of decimal places. Default 0.
|
||||
*
|
||||
* @return string Number string on success.
|
||||
*/
|
||||
function size_format_zero( $bytes, $decimals = 0 ) {
|
||||
if ( $bytes > 0 ) {
|
||||
return size_format( $bytes, $decimals );
|
||||
} else {
|
||||
return '0 GB';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds settings links to plugin row.
|
||||
*/
|
||||
function plugins_list_links( $actions ) {
|
||||
// Build and escape the URL.
|
||||
$url = esc_url( $this->settings_url() );
|
||||
|
||||
// Create the link.
|
||||
$custom_links = [];
|
||||
if ( $this->api->has_token() ) {
|
||||
$custom_links['settings'] = "<a href='$url'>" . esc_html__( 'Settings', 'infinite-uploads' ) . '</a>';
|
||||
} else {
|
||||
$custom_links['connect'] = "<a href='$url' style='color: #EE7C1E;'>" . esc_html__( 'Connect', 'infinite-uploads' ) . '</a>';
|
||||
}
|
||||
$custom_links['support'] = '<a href="' . esc_url( $this->api_url( '/support/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin&utm_term=support&utm_content=meta' ) ) . '">' . esc_html__( 'Support', 'infinite-uploads' ) . '</a>';
|
||||
|
||||
// Replace deactivate link if they haven't disconnected yet.
|
||||
if ( array_key_exists( 'deactivate', $actions ) ) {
|
||||
if ( $this->api->has_token() && $this->api->get_site_data() ) {
|
||||
$actions['deactivate'] = sprintf(
|
||||
'<a href="%s" aria-label="%s">%s</a>',
|
||||
$url . "&deactivate-notice=1",
|
||||
/* translators: %s: Plugin name. */
|
||||
esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), __( 'Infinite Uploads', 'infinite-uploads' ) ) ),
|
||||
__( 'Deactivate' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the links to the beginning of the array.
|
||||
return array_merge( $custom_links, $actions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings url with optional url args.
|
||||
*
|
||||
* @param array $args Optional. Same as for add_query_arg()
|
||||
*
|
||||
* @return string Unescaped url to settings page.
|
||||
*/
|
||||
function settings_url( $args = [] ) {
|
||||
if ( is_multisite() ) {
|
||||
$base = network_admin_url( 'admin.php?page=infinite_uploads' );
|
||||
} else {
|
||||
$base = admin_url( 'admin.php?page=infinite_uploads' );
|
||||
}
|
||||
|
||||
return add_query_arg( $args, $base );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a url to the public Infinite Uploads site.
|
||||
*
|
||||
* @param string $path Optional path on the site.
|
||||
*
|
||||
* @return Infinite_Uploads_Api_Handler|string
|
||||
*/
|
||||
function api_url( $path = '' ) {
|
||||
$url = trailingslashit( $this->api->server_root );
|
||||
|
||||
if ( $path && is_string( $path ) ) {
|
||||
$url .= ltrim( $path, '/' );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new settings page under Settings.
|
||||
*/
|
||||
function admin_menu() {
|
||||
$page = add_menu_page(
|
||||
__( 'Infinite Uploads', 'infinite-uploads' ),
|
||||
__( 'Infinite Uploads', 'infinite-uploads' ),
|
||||
$this->iup_instance->capability,
|
||||
'infinite_uploads',
|
||||
[
|
||||
$this,
|
||||
'settings_page',
|
||||
],
|
||||
plugins_url( 'assets/img/iu-logo-blue-sm.svg', __FILE__ )
|
||||
);
|
||||
|
||||
add_action( 'admin_print_scripts-' . $page, [ &$this, 'admin_scripts' ] );
|
||||
add_action( 'admin_print_styles-' . $page, [ &$this, 'admin_styles' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function admin_scripts() {
|
||||
wp_enqueue_script( 'iup-bootstrap', plugins_url( 'assets/bootstrap/js/bootstrap.bundle.min.js', __FILE__ ), [ 'jquery' ], INFINITE_UPLOADS_VERSION );
|
||||
wp_enqueue_script( 'iup-chartjs', plugins_url( 'assets/js/Chart.min.js', __FILE__ ), [], INFINITE_UPLOADS_VERSION );
|
||||
wp_enqueue_script( 'iup-js', plugins_url( 'assets/js/infinite-uploads.js', __FILE__ ), [ 'wp-color-picker' ], INFINITE_UPLOADS_VERSION );
|
||||
|
||||
$data = [];
|
||||
$data['strings'] = [
|
||||
'leave_confirm' => esc_html__( 'Are you sure you want to leave this tab? The current bulk action will be canceled and you will need to continue where it left off later.', 'infinite-uploads' ),
|
||||
'ajax_error' => esc_html__( 'Too many server errors. Please try again.', 'infinite-uploads' ),
|
||||
'leave_confirmation' => esc_html__( 'If you leave this page the sync will be interrupted and you will have to continue where you left off later.', 'infinite-uploads' ),
|
||||
];
|
||||
|
||||
$data['local_types'] = $this->iup_instance->get_filetypes( true );
|
||||
|
||||
$api_data = $this->api->get_site_data();
|
||||
if ( $this->api->has_token() && $api_data ) {
|
||||
$data['cloud_types'] = $this->iup_instance->get_filetypes( true, $api_data->stats->site->types );
|
||||
}
|
||||
|
||||
$data['nonce'] = [
|
||||
'scan' => wp_create_nonce( 'iup_scan' ),
|
||||
'sync' => wp_create_nonce( 'iup_sync' ),
|
||||
'delete' => wp_create_nonce( 'iup_delete' ),
|
||||
'download' => wp_create_nonce( 'iup_download' ),
|
||||
'toggle' => wp_create_nonce( 'iup_toggle' ),
|
||||
'video' => wp_create_nonce( 'iup_video' ),
|
||||
];
|
||||
|
||||
wp_localize_script( 'iup-js', 'iup_data', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the bulk Deactivate button from Plugins list.
|
||||
*/
|
||||
function block_bulk_deactivate( $plugin ) {
|
||||
if ( ( ( isset( $_POST['action'] ) && 'deactivate-selected' === $_POST['action'] ) || ( isset( $_POST['action2'] ) && 'deactivate-selected' === $_POST['action2'] ) ) && 'infinite-uploads/infinite-uploads.php' === $plugin ) {
|
||||
if ( $this->api->has_token() && $this->api->get_site_data() ) {
|
||||
wp_redirect( $this->settings_url( [ 'deactivate-notice' => 1 ] ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function admin_styles() {
|
||||
wp_enqueue_style( 'wp-color-picker' );
|
||||
wp_enqueue_style( 'iup-bootstrap', plugins_url( 'assets/bootstrap/css/bootstrap.min.css', __FILE__ ), false, INFINITE_UPLOADS_VERSION );
|
||||
wp_enqueue_style( 'iup-styles', plugins_url( 'assets/css/admin.css', __FILE__ ), [ 'iup-bootstrap' ], INFINITE_UPLOADS_VERSION );
|
||||
|
||||
//hide all admin notices from another source on these pages
|
||||
//remove_all_actions( 'admin_notices' );
|
||||
//remove_all_actions( 'network_admin_notices' );
|
||||
//remove_all_actions( 'all_admin_notices' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for temp_token in url and processes auth if present.
|
||||
*/
|
||||
function intercept_auth() {
|
||||
if ( ! current_user_can( $this->iup_instance->capability ) ) {
|
||||
wp_die( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $_GET['temp_token'] ) ) {
|
||||
$result = $this->api->authorize( $_GET['temp_token'] );
|
||||
if ( ! $result ) {
|
||||
$this->auth_error = $this->api->api_error;
|
||||
} else {
|
||||
wp_safe_redirect( $this->settings_url() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $_GET['clear'] ) ) {
|
||||
delete_site_option( 'iup_files_scanned' );
|
||||
wp_safe_redirect( $this->settings_url() );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['refresh'] ) ) {
|
||||
$this->api->get_site_data( true );
|
||||
wp_safe_redirect( $this->settings_url() );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['reinstall'] ) ) {
|
||||
infinite_uploads_install();
|
||||
wp_safe_redirect( $this->settings_url() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings page display callback.
|
||||
*/
|
||||
function settings_page() {
|
||||
global $wpdb;
|
||||
|
||||
$region_labels = [
|
||||
'US' => esc_html__( 'United States', 'infinite-uploads' ),
|
||||
'EU' => esc_html__( 'Europe', 'infinite-uploads' ),
|
||||
];
|
||||
|
||||
$stats = $this->iup_instance->get_sync_stats();
|
||||
$api_data = $this->api->get_site_data();
|
||||
?>
|
||||
<div id="iup-settings-page" class="wrap iup-background">
|
||||
|
||||
<h1>
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/iu-logo-words.svg', __FILE__ ) ); ?>" alt="Infinite Uploads Logo" height="50" width="200"/>
|
||||
</h1>
|
||||
|
||||
<?php if ( $this->auth_error ) { ?>
|
||||
<div class="alert alert-danger mt-1 alert-dismissible fade show" role="alert">
|
||||
<?php echo esc_html( $this->auth_error ); ?>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div id="iup-error" class="alert alert-danger mt-1" role="alert"></div>
|
||||
|
||||
<?php if ( isset( $api_data->site ) && ! $api_data->site->cdn_enabled ) { ?>
|
||||
<div class="alert alert-warning mt-1" role="alert">
|
||||
<?php printf( __( "Files can't be uploaded and your CDN is disabled due to a billing issue with your Infinite Uploads account. Please <a href='%s' class='alert-link'>visit your account page</a> to fix, or disconnect this site from the cloud. Images and links to media on your site may be broken until you take action. <a href='%s' class='alert-link' data-toggle='tooltip' title='Refresh account data'>Already fixed?</a>", 'infinite-uploads' ), esc_url( $this->api_url( '/account/billing/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin' ) ), esc_url( $this->settings_url( [ 'refresh' => 1 ] ) ) ); ?>
|
||||
</div>
|
||||
<?php } elseif ( isset( $api_data->site ) && ! $api_data->site->upload_writeable ) { ?>
|
||||
<div class="alert alert-warning mt-1" role="alert">
|
||||
<?php printf( __( "Files can't be uploaded and your CDN will be disabled soon due to a billing issue with your Infinite Uploads account. Please <a href='%s' class='alert-link'>visit your account page</a> to fix, or disconnect this site from the cloud. <a href='%s' class='alert-link' data-toggle='tooltip' title='Refresh account data'>Already fixed?</a>", 'infinite-uploads' ), esc_url( $this->api_url( '/account/billing/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin' ) ), esc_url( $this->settings_url( [ 'refresh' => 1 ] ) ) ); ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ( isset( $_GET['deactivate-notice'] ) && $this->api->has_token() && $api_data ) { ?>
|
||||
<div class="alert alert-warning mt-1" role="alert">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md col-12 mb-md-0 mb-2">
|
||||
<?php _e( "There is uploaded media from your site that may only exist in the Infinite Uploads cloud. <strong>You MUST download your media files before deactivating this plugin to prevent data loss!</strong>", 'infinite-uploads' ); ?>
|
||||
</div>
|
||||
<div class="col-sm-4 col-lg-3 text-md-right">
|
||||
<button class="btn text-nowrap btn-info" data-toggle="modal" data-target="#scan-remote-modal" data-next="download"><?php esc_html_e( 'Download & Disconnect', 'infinite-uploads' ); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
if ( $this->api->has_token() && $api_data ) {
|
||||
if ( ! $api_data->stats->site->files ) {
|
||||
$synced = $wpdb->get_row( "SELECT count(*) AS files, SUM(`size`) as size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1" );
|
||||
$cloud_size = $synced->size;
|
||||
$cloud_files = $synced->files;
|
||||
$cloud_total_size = $api_data->stats->cloud->storage + $synced->size;
|
||||
} else {
|
||||
$cloud_size = $api_data->stats->site->storage;
|
||||
$cloud_files = $api_data->stats->site->files;
|
||||
$cloud_total_size = $api_data->stats->cloud->storage;
|
||||
}
|
||||
|
||||
require_once( dirname( __FILE__ ) . '/templates/header-columns.php' );
|
||||
|
||||
if ( ! infinite_uploads_enabled() ) {
|
||||
require_once( dirname( __FILE__ ) . '/templates/modal-scan.php' );
|
||||
if ( isset( $api_data->site ) && $api_data->site->upload_writeable ) {
|
||||
require_once( dirname( __FILE__ ) . '/templates/modal-upload.php' );
|
||||
require_once( dirname( __FILE__ ) . '/templates/modal-enable.php' );
|
||||
}
|
||||
}
|
||||
|
||||
require_once( dirname( __FILE__ ) . '/templates/settings.php' );
|
||||
|
||||
require_once( dirname( __FILE__ ) . '/templates/modal-remote-scan.php' );
|
||||
require_once( dirname( __FILE__ ) . '/templates/modal-delete.php' );
|
||||
require_once( dirname( __FILE__ ) . '/templates/modal-download.php' );
|
||||
|
||||
} else {
|
||||
if ( ! empty( $stats['files_finished'] ) && $stats['files_finished'] >= ( time() - DAY_IN_SECONDS ) ) {
|
||||
$to_sync = $wpdb->get_row( "SELECT count(*) AS files, SUM(`size`) as size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE deleted = 0" );
|
||||
require_once( dirname( __FILE__ ) . '/templates/connect.php' );
|
||||
} else {
|
||||
//Make sure table is installed so we can show an error if not.
|
||||
if ( ! $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->base_prefix}infinite_uploads_files'" ) ) {
|
||||
infinite_uploads_install();
|
||||
if ( ! $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->base_prefix}infinite_uploads_files'" ) ) {
|
||||
require_once( dirname( __FILE__ ) . '/templates/install-error.php' );
|
||||
} else {
|
||||
require_once( dirname( __FILE__ ) . '/templates/welcome.php' );
|
||||
}
|
||||
} else {
|
||||
require_once( dirname( __FILE__ ) . '/templates/welcome.php' );
|
||||
}
|
||||
}
|
||||
require_once( dirname( __FILE__ ) . '/templates/modal-scan.php' );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
require_once( dirname( __FILE__ ) . '/templates/footer.php' );
|
||||
}
|
||||
}
|
@ -1,535 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* API module.
|
||||
* Handles all functions that are executing remote calls.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The main API class.
|
||||
*/
|
||||
class Infinite_Uploads_Api_Handler {
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* The API server.
|
||||
*
|
||||
* @var string (URL)
|
||||
*/
|
||||
public $server_root = 'https://infiniteuploads.com/';
|
||||
|
||||
/**
|
||||
* Path to the REST API on the server.
|
||||
*
|
||||
* @var string (URL)
|
||||
*/
|
||||
protected $rest_api = 'api/v1/';
|
||||
|
||||
/**
|
||||
* The complete REST API endpoint. Defined in constructor.
|
||||
*
|
||||
* @var string (URL)
|
||||
*/
|
||||
protected $server_url = '';
|
||||
|
||||
/**
|
||||
* Stores the API token used for authentication.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $api_token = '';
|
||||
|
||||
/**
|
||||
* Stores the site_id from the API.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $api_site_id = '';
|
||||
|
||||
/**
|
||||
* Holds the last API error that occured (if any)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $api_error = '';
|
||||
|
||||
private $iup_instance;
|
||||
|
||||
/**
|
||||
* Set up the API module.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->iup_instance = Infinite_Uploads::get_instance();
|
||||
|
||||
if ( defined( 'INFINITE_UPLOADS_CUSTOM_API_SERVER' ) ) {
|
||||
$this->server_root = trailingslashit( INFINITE_UPLOADS_CUSTOM_API_SERVER );
|
||||
}
|
||||
$this->server_url = $this->server_root . $this->rest_api;
|
||||
|
||||
$this->api_token = get_site_option( 'iup_apitoken' );
|
||||
$this->api_site_id = get_site_option( 'iup_site_id' );
|
||||
|
||||
// Schedule automatic data update on the main site of the network.
|
||||
if ( is_main_site() ) {
|
||||
if ( ! wp_next_scheduled( 'infinite_uploads_sync' ) ) {
|
||||
wp_schedule_event( time(), 'daily', 'infinite_uploads_sync' );
|
||||
}
|
||||
|
||||
add_action( 'infinite_uploads_sync', [ $this, 'get_site_data' ] );
|
||||
add_action( 'wp_ajax_nopriv_infinite-uploads-refresh', [ &$this, 'remote_refresh' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Infinite_Uploads_Api_Handler
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new Infinite_Uploads_Api_Handler();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the API token is defined.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_token() {
|
||||
return ! empty( $this->api_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the API token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_token() {
|
||||
return $this->api_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the API token in the database.
|
||||
*
|
||||
* @param string $token The new API token to store.
|
||||
*/
|
||||
public function set_token( $token ) {
|
||||
$this->api_token = $token;
|
||||
update_site_option( 'iup_apitoken', $token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the site_id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_site_id() {
|
||||
return $this->api_site_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the API site_id in the database.
|
||||
*
|
||||
* @param int $site_id The new site_id to store.
|
||||
*/
|
||||
public function set_site_id( $site_id ) {
|
||||
$this->api_site_id = $site_id;
|
||||
update_site_option( 'iup_site_id', $site_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical site_url that should be used for the site on the site.
|
||||
*
|
||||
* Define INFINITE_UPLOADS_SITE_URL to override or make static the url it should show as
|
||||
* in the site. Defaults to network_site_url() which may be dynamically filtered
|
||||
* by some plugins and hosting providers.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function network_site_url() {
|
||||
return defined( 'INFINITE_UPLOADS_SITE_URL' ) ? INFINITE_UPLOADS_SITE_URL : network_site_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical home_url that should be used for the site on the site.
|
||||
*
|
||||
* Define INFINITE_UPLOADS_HUB_HOME_URL to override or make static the url it should show as
|
||||
* in the site. Defaults to network_home_url() which may be dynamically filtered
|
||||
* by some plugins and hosting providers.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function network_home_url() {
|
||||
if ( defined( 'INFINITE_UPLOADS_HOME_URL' ) ) {
|
||||
return INFINITE_UPLOADS_HOME_URL;
|
||||
} else {
|
||||
return network_home_url();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full URL to the specified REST API endpoint.
|
||||
*
|
||||
* This is a function instead of making the property $server_url public so
|
||||
* we have better control and overview of the requested pages:
|
||||
* It's easy to add a filter or add extra URL params to all URLs this way.
|
||||
*
|
||||
* @param string $endpoint The endpoint to call on the server.
|
||||
*
|
||||
* @return string The full URL to the requested endpoint.
|
||||
*/
|
||||
public function rest_url( $endpoint ) {
|
||||
if ( preg_match( '!^https?://!', $endpoint ) ) {
|
||||
$url = $endpoint;
|
||||
} else {
|
||||
$url = $this->server_url . $endpoint;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an API call and returns the results.
|
||||
*
|
||||
* @param string $remote_path The API function to call.
|
||||
* @param array $data Optional. GET or POST data to send.
|
||||
* @param string $method Optional. GET or POST.
|
||||
* @param array $options Optional. Array of request options.
|
||||
*
|
||||
* @return object|boolean Results of the API call response body.
|
||||
*/
|
||||
public function call( $remote_path, $data = [], $method = 'GET', $options = [] ) {
|
||||
$link = $this->rest_url( $remote_path );
|
||||
|
||||
$options = wp_parse_args(
|
||||
$options,
|
||||
[
|
||||
'timeout' => 25,
|
||||
'user-agent' => 'Infinite Uploads/' . INFINITE_UPLOADS_VERSION . ' (+' . network_site_url() . ')',
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if ( $this->has_token() ) {
|
||||
$options['headers']['Authorization'] = 'Bearer ' . $this->get_token();
|
||||
}
|
||||
|
||||
if ( 'GET' == $method ) {
|
||||
if ( ! empty( $data ) ) {
|
||||
$link = add_query_arg( $data, $link );
|
||||
}
|
||||
$response = wp_remote_get( $link, $options );
|
||||
} elseif ( 'POST' == $method ) {
|
||||
$options['body'] = json_encode( $data );
|
||||
$response = wp_remote_post( $link, $options );
|
||||
}
|
||||
|
||||
// Add the request-URL to the response data.
|
||||
if ( $response && is_array( $response ) ) {
|
||||
$response['request_url'] = $link;
|
||||
}
|
||||
|
||||
if ( defined( 'INFINITE_UPLOADS_API_DEBUG' ) && INFINITE_UPLOADS_API_DEBUG ) {
|
||||
$log = '[INFINITE_UPLOADS API call] %s | %s: %s (%s)';
|
||||
if ( defined( 'INFINITE_UPLOADS_API_DEBUG_ALL' ) && INFINITE_UPLOADS_API_DEBUG_ALL ) {
|
||||
$log .= "\nRequest options: %s\nResponse: %s";
|
||||
}
|
||||
|
||||
$resp_body = wp_remote_retrieve_body( $response );
|
||||
|
||||
if ( $response && is_array( $response ) ) {
|
||||
$debug_data = sprintf( "%s %s\n", wp_remote_retrieve_response_code( $response ), wp_remote_retrieve_response_message( $response ) );
|
||||
$debug_data .= var_export( wp_remote_retrieve_headers( $response ), true ) . PHP_EOL; // WPCS: var_export() ok.
|
||||
$debug_data .= $resp_body;
|
||||
} else {
|
||||
$debug_data = '';
|
||||
}
|
||||
|
||||
$msg = sprintf(
|
||||
$log,
|
||||
INFINITE_UPLOADS_VERSION,
|
||||
$method,
|
||||
$link,
|
||||
wp_remote_retrieve_response_code( $response ),
|
||||
wp_json_encode( $options ),
|
||||
$debug_data
|
||||
);
|
||||
error_log( $msg );
|
||||
}
|
||||
|
||||
//if there is an auth problem
|
||||
if ( $this->has_token() && in_array( wp_remote_retrieve_response_code( $response ), [ 401, 403, 404 ] ) ) {
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ) );
|
||||
if ( isset( $body->code ) && in_array( $body->code, [ 'missing_api_token', 'invalid_site', 'invalid_api_key' ] ) ) {
|
||||
$this->set_token( '' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
|
||||
if ( ! isset( $options['blocking'] ) || false !== $options['blocking'] ) {
|
||||
$this->parse_api_error( $response );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ) );
|
||||
if ( json_last_error() ) {
|
||||
$this->parse_api_error( json_last_error_msg() );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an HTTP response object (or other value) to determine an error
|
||||
* reason. The error reason is added to the PHP error log.
|
||||
*
|
||||
* @param string|WP_Error|array $response String, WP_Error object, HTTP response array.
|
||||
*/
|
||||
protected function parse_api_error( $response ) {
|
||||
$error_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( ! $error_code ) {
|
||||
$error_code = 500;
|
||||
}
|
||||
$this->api_error = '';
|
||||
|
||||
$body = is_array( $response )
|
||||
? wp_remote_retrieve_body( $response )
|
||||
: false;
|
||||
|
||||
if ( is_scalar( $response ) ) {
|
||||
$this->api_error = $response;
|
||||
} elseif ( is_wp_error( $response ) ) {
|
||||
$this->api_error = $response->get_error_message();
|
||||
} elseif ( is_array( $response ) && ! empty( $body ) ) {
|
||||
$data = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
if ( is_array( $data ) && ! empty( $data['message'] ) ) {
|
||||
$this->api_error = $data['message'];
|
||||
}
|
||||
}
|
||||
|
||||
$url = '(unknown URL)';
|
||||
if ( is_array( $response ) && isset( $response['request_url'] ) ) {
|
||||
$url = $response['request_url'];
|
||||
}
|
||||
|
||||
if ( empty( $this->api_error ) ) {
|
||||
$this->api_error = sprintf(
|
||||
'HTTP Error: %s "%s"',
|
||||
$error_code,
|
||||
wp_remote_retrieve_response_message( $response )
|
||||
);
|
||||
}
|
||||
|
||||
// Collect back-trace information for the logfile.
|
||||
$caller_dump = '';
|
||||
if ( defined( 'INFINITE_UPLOADS_API_DEBUG' ) && INFINITE_UPLOADS_API_DEBUG ) {
|
||||
$trace = debug_backtrace();
|
||||
$caller = [];
|
||||
$last_line = '';
|
||||
foreach ( $trace as $level => $item ) {
|
||||
if ( ! isset( $item['class'] ) ) {
|
||||
$item['class'] = '';
|
||||
}
|
||||
if ( ! isset( $item['type'] ) ) {
|
||||
$item['type'] = '';
|
||||
}
|
||||
if ( ! isset( $item['function'] ) ) {
|
||||
$item['function'] = '<function>';
|
||||
}
|
||||
if ( ! isset( $item['line'] ) ) {
|
||||
$item['line'] = '?';
|
||||
}
|
||||
|
||||
if ( $level > 0 ) {
|
||||
$caller[] = $item['class'] .
|
||||
$item['type'] .
|
||||
$item['function'] .
|
||||
':' . $last_line;
|
||||
}
|
||||
$last_line = $item['line'];
|
||||
}
|
||||
$caller_dump = "\n\t# " . implode( "\n\t# ", $caller );
|
||||
|
||||
if ( is_array( $response ) && isset( $response['request_url'] ) ) {
|
||||
$caller_dump = "\n\tURL: " . $response['request_url'] . $caller_dump;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the error to PHP error log.
|
||||
error_log(
|
||||
sprintf(
|
||||
'[INFINITE_UPLOADS API Error] %s | %s (%s [%s]) %s',
|
||||
INFINITE_UPLOADS_VERSION,
|
||||
$this->api_error,
|
||||
$url,
|
||||
$error_code,
|
||||
$caller_dump
|
||||
),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform the initial Oauth activation for API.
|
||||
*
|
||||
* @param $temp_token
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize( $temp_token ) {
|
||||
$result = $this->call( 'token', [ 'temp_token' => $temp_token ], 'POST' );
|
||||
if ( $result ) {
|
||||
$this->set_token( $result->api_token );
|
||||
$this->set_site_id( $result->site_id );
|
||||
|
||||
return $this->get_site_data( true );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site data from API, normally cached for 12hrs.
|
||||
*
|
||||
* @param bool $force_refresh
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function get_site_data( $force_refresh = false ) {
|
||||
|
||||
if ( ! $this->has_token() || ! $this->get_site_id() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $force_refresh ) {
|
||||
$data = get_site_option( 'iup_api_data' );
|
||||
if ( $data ) {
|
||||
$data = json_decode( $data );
|
||||
|
||||
if ( $data->refreshed >= ( time() - HOUR_IN_SECONDS * 12 ) ) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$result = $this->call( "site/" . $this->get_site_id(), [], 'GET' );
|
||||
if ( $result ) {
|
||||
$result->refreshed = time();
|
||||
//json_encode to prevent object injections
|
||||
update_site_option( 'iup_api_data', json_encode( $result ) );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $data; //if a temp API issue default to using cached data
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge a list of urls from the CDN. We don't need to wait for a response from this so make it async.
|
||||
*
|
||||
* @param array $urls
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function purge( $urls ) {
|
||||
return $this->call( "site/" . $this->get_site_id() . "/purge", [ 'urls' => $urls ], 'POST', [
|
||||
'timeout' => 0.01,
|
||||
'blocking' => false,
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for remote ping from API telling us to refresh data.
|
||||
*
|
||||
* The security of this doesn't have to be perfect, we just want to stop any possible DoS vector.
|
||||
*/
|
||||
public function remote_refresh( $urls ) {
|
||||
|
||||
if ( ! $this->has_token() ) {
|
||||
wp_send_json_error( [ 'code' => 'disconnected', 'message' => 'Site is disconnected from API' ] );
|
||||
}
|
||||
|
||||
if ( empty( $_SERVER['HTTP_SIGNATURE'] ) || ! preg_match( '/[a-f0-9]{64}/', $_SERVER['HTTP_SIGNATURE'], $matches ) ) {
|
||||
wp_send_json_error( [ 'code' => 'missing_auth_header', 'message' => 'Missing authentication header' ] );
|
||||
}
|
||||
|
||||
$hash = $matches[0]; //an SHA256 hash of request
|
||||
$token = hash( 'sha256', $this->get_token() );
|
||||
|
||||
$site_id = sanitize_key( $_POST['site_id'] );
|
||||
$hash_string = sanitize_key( $_POST['req_id'] ) . $site_id;
|
||||
|
||||
$valid = hash_hmac( 'sha256', $hash_string, $token );
|
||||
|
||||
$is_valid = hash_equals( $this->get_site_id(), $site_id ) && hash_equals( $valid, $hash ); //Timing attack safe string comparison, PHP <5.6 compat added in WP 3.9.2
|
||||
if ( ! $is_valid ) {
|
||||
wp_send_json_error(
|
||||
[ 'code' => 'incorrect_auth', 'message' => 'Incorrect authentication' ]
|
||||
);
|
||||
}
|
||||
|
||||
$this->get_site_data( true );
|
||||
|
||||
|
||||
if ( defined( 'INFINITE_UPLOADS_API_DEBUG' ) && INFINITE_UPLOADS_API_DEBUG ) {
|
||||
$log = '[INFINITE_UPLOADS API remote call] %s | %s';
|
||||
|
||||
$msg = sprintf(
|
||||
$log,
|
||||
INFINITE_UPLOADS_VERSION,
|
||||
$_REQUEST['action']
|
||||
);
|
||||
error_log( $msg );
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from API
|
||||
*/
|
||||
public function disconnect() {
|
||||
global $wpdb;
|
||||
|
||||
//ping the API to let them know we've disconnected
|
||||
$this->call( "site/" . $this->get_site_id() . "/disconnect", [], 'POST', [
|
||||
'timeout' => 0.01,
|
||||
'blocking' => false,
|
||||
] );
|
||||
|
||||
//Do a find replace on the posts table. For multisite or other tables would really need a big find-replace plugin or WP CLI.
|
||||
$uploads_url = $this->iup_instance->get_original_upload_dir_root();
|
||||
$api_data = $this->get_site_data();
|
||||
|
||||
if ( $api_data ) {
|
||||
$replace = trailingslashit( $uploads_url['baseurl'] );
|
||||
|
||||
$find = 'https://' . trailingslashit( $api_data->site->cname );
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET `post_content` = replace(`post_content`, %s, %s)", $find, $replace ) );
|
||||
|
||||
if ( $api_data->site->cdn_url != $api_data->site->cname ) {
|
||||
$find = 'https://' . trailingslashit( $api_data->site->cdn_url );
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET `post_content` = replace(`post_content`, %s, %s)", $find, $replace ) );
|
||||
}
|
||||
wp_cache_flush(); //unfortunately no other way to clean every post from cache.
|
||||
}
|
||||
|
||||
//logout and disable
|
||||
$this->set_token( '' ); //logout
|
||||
$this->iup_instance->toggle_cloud( false );
|
||||
delete_site_option( 'iup_files_scanned' );
|
||||
}
|
||||
}
|
@ -1,260 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Lists files using a Breadth-First search algorithm to allow for time limits and resume across multiple requests.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Infinite_Uploads_Filelist
|
||||
*/
|
||||
class Infinite_Uploads_Filelist {
|
||||
|
||||
public $is_done = false;
|
||||
public $paths_left = [];
|
||||
public $file_count = 0;
|
||||
public $file_list = [];
|
||||
public $exclusions = [
|
||||
'/et-cache/',
|
||||
'/et_temp/',
|
||||
'/imports/',
|
||||
'/cache/',
|
||||
'/wp-defender/',
|
||||
'.DS_Store',
|
||||
];
|
||||
protected $root_path;
|
||||
protected $timeout;
|
||||
protected $start_time;
|
||||
protected $instance;
|
||||
protected $insert_rows = 500;
|
||||
|
||||
/**
|
||||
* Infinite_Uploads_Filelist constructor.
|
||||
*
|
||||
* @param string $root_path The full path of the directory to iterate.
|
||||
* @param float $timeout Timeout in seconds.
|
||||
* @param array $paths_left Provide as returned if continuing the filelist after a timeout.
|
||||
*/
|
||||
public function __construct( $root_path, $timeout = 25.0, $paths_left = [] ) {
|
||||
$this->root_path = rtrim( $root_path, '/' ); //expected no trailing slash.
|
||||
$this->timeout = $timeout;
|
||||
$this->paths_left = $paths_left;
|
||||
$this->instance = Infinite_Uploads::get_instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs over the site's files.
|
||||
*/
|
||||
public function start() {
|
||||
global $wpdb;
|
||||
$this->start_time = microtime( true );
|
||||
$this->file_count = 0;
|
||||
$this->file_list = [];
|
||||
|
||||
// If just starting reset the local DB list storage
|
||||
if ( empty( $this->paths_left ) ) {
|
||||
//TRUNCATE is fastest, try it first
|
||||
$result = $wpdb->query( "TRUNCATE TABLE {$wpdb->base_prefix}infinite_uploads_files" );
|
||||
//Sometimes hosts don't give the DB user TRUNCATE permissions, so DELETE all if we have to.
|
||||
if ( false === $result ) {
|
||||
$wpdb->query( "DELETE FROM {$wpdb->base_prefix}infinite_uploads_files WHERE 1" );
|
||||
}
|
||||
|
||||
update_site_option( 'iup_files_scanned', [
|
||||
'files_started' => time(),
|
||||
'files_finished' => false,
|
||||
'compare_started' => false,
|
||||
'compare_finished' => false,
|
||||
'sync_started' => false,
|
||||
'sync_finished' => false,
|
||||
'download_started' => false,
|
||||
'download_finished' => false,
|
||||
] );
|
||||
}
|
||||
|
||||
$this->get_files();
|
||||
|
||||
$this->flush_to_db();
|
||||
|
||||
if ( empty( $this->paths_left ) ) {
|
||||
// So we are done. Say so.
|
||||
$this->is_done = true;
|
||||
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['files_finished'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a breadth-first iteration on all files and gathers the relevant info for each one.
|
||||
*
|
||||
* @todo test what happens if some files have no read permissions.
|
||||
*/
|
||||
protected function get_files() {
|
||||
|
||||
$paths = ( empty( $this->paths_left ) ) ? [ $this->root_path ] : $this->paths_left;
|
||||
|
||||
while ( ! empty( $paths ) ) {
|
||||
$path = array_pop( $paths );
|
||||
|
||||
// Skip ".." items.
|
||||
if ( preg_match( '/\.\.([\/\\\\]|$)/', $path ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 0 !== strpos( $path, $this->root_path ) ) {
|
||||
// Build the absolute path in case it's not the first iteration.
|
||||
$path = rtrim( $this->root_path, '/' ) . $path;
|
||||
}
|
||||
|
||||
if ( $this->is_excluded( $path ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contents = defined( 'GLOB_BRACE' )
|
||||
? glob( trailingslashit( $path ) . '{,.}[!.,!..]*', GLOB_BRACE )
|
||||
: glob( trailingslashit( $path ) . '[!.,!..]*' );
|
||||
|
||||
foreach ( $contents as $item ) {
|
||||
$file = [];
|
||||
if ( is_link( $item ) || $this->is_excluded( $item ) ) {
|
||||
continue;
|
||||
} elseif ( is_file( $item ) ) {
|
||||
if ( is_readable( $item ) ) {
|
||||
$file = $this->get_file_info( $item );
|
||||
} else {
|
||||
$file = null;
|
||||
error_log( sprintf( '[INFINITE_UPLOADS Filelist Error] %s could not be read for syncing', $item ) );
|
||||
}
|
||||
|
||||
$file['name'] = $this->relative_path( $item );
|
||||
|
||||
$this->add_file( $file );
|
||||
} elseif ( is_dir( $item ) ) {
|
||||
if ( ! in_array( $item, $paths, true ) ) {
|
||||
$paths[] = $this->relative_path( $item );
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->paths_left = $paths;
|
||||
|
||||
// If we have exceed the imposed time limit, lets pause the iteration here.
|
||||
if ( $this->has_exceeded_timelimit() ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->is_done = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks path against excluded pattern.
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
protected function is_excluded( $path ) {
|
||||
/**
|
||||
* Filters the built in list of file/directory exclusions that should not be synced to the Infinite Uploads cloud. Be specific it's a simple strpos() search for the strings.
|
||||
*
|
||||
* @param {array} $exclusions A list of file or directory names in the format of `/do-not-sync-this-dir/` or `somefilename.ext`.
|
||||
*
|
||||
* @return {array} A list of file or directory names in the format of `/do-not-sync-this-dir/` or `somefilename.ext`.
|
||||
* @since 1.0
|
||||
* @hook infinite_uploads_sync_exclusions
|
||||
*
|
||||
*/
|
||||
$exclusions = apply_filters( 'infinite_uploads_sync_exclusions', $this->exclusions );
|
||||
foreach ( $exclusions as $string ) {
|
||||
if ( false !== strpos( $path, $string ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks file health and returns as many info as it can.
|
||||
*
|
||||
* @param string $item The file to be investigated.
|
||||
*
|
||||
* @return mixed File info or false for failure.
|
||||
*/
|
||||
protected function get_file_info( $item ) {
|
||||
$file = [];
|
||||
$file['mtime'] = filemtime( $item );
|
||||
//$file['md5'] = md5_file( $item );
|
||||
$file['size'] = filesize( $item );
|
||||
$file['type'] = $this->instance->get_file_type( $item );
|
||||
|
||||
if ( empty( $file['mtime'] ) && empty( $file['size'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns rel path of file/dir, relative to site root.
|
||||
*
|
||||
* @param string $item File's absolute path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function relative_path( $item ) {
|
||||
// Retrieve the relative to the site root path of the file.
|
||||
$pos = strpos( $item, $this->root_path );
|
||||
if ( 0 === $pos ) {
|
||||
return substr_replace( $item, '', $pos, strlen( $this->root_path ) );
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file details to internal storage and the db.
|
||||
*/
|
||||
protected function add_file( $file ) {
|
||||
$this->file_list[] = $file;
|
||||
$this->file_count ++;
|
||||
if ( count( $this->file_list ) >= $this->insert_rows ) {
|
||||
$this->flush_to_db();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the queued file list to DB storage.
|
||||
*/
|
||||
protected function flush_to_db() {
|
||||
global $wpdb;
|
||||
|
||||
if ( count( $this->file_list ) ) {
|
||||
$values = [];
|
||||
foreach ( $this->file_list as $file ) {
|
||||
$values[] = $wpdb->prepare( "(%s,%d,%d,%s,0)", $file['name'], $file['size'], $file['mtime'], $file['type'] );
|
||||
}
|
||||
|
||||
$query = "INSERT INTO {$wpdb->base_prefix}infinite_uploads_files (file, size, modified, type, errors) VALUES ";
|
||||
$query .= implode( ",\n", $values );
|
||||
$query .= " ON DUPLICATE KEY UPDATE size = VALUES(size), modified = VALUES(modified), type = VALUES(type), errors = 0";
|
||||
if ( $wpdb->query( $query ) ) {
|
||||
$this->file_list = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current iteration has exceeded the given time limit.
|
||||
*
|
||||
* @return bool True if we have exceeded the time limit, false if we haven't.
|
||||
*/
|
||||
protected function has_exceeded_timelimit() {
|
||||
$current_time = microtime( true );
|
||||
$time_diff = number_format( $current_time - $this->start_time, 2 );
|
||||
|
||||
$has_exceeded_timelimit = ! empty( $this->timeout ) && ( $time_diff > $this->timeout );
|
||||
|
||||
return $has_exceeded_timelimit;
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
<?php
|
||||
|
||||
class Infinite_Uploads_Image_Editor_Imagick extends WP_Image_Editor_Imagick {
|
||||
|
||||
protected $temp_file_to_cleanup = null;
|
||||
|
||||
/**
|
||||
* Hold on to a reference of all temp local files.
|
||||
*
|
||||
* These are cleaned up on __destruct.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $temp_files_to_cleanup = [];
|
||||
|
||||
/**
|
||||
* Loads image from $this->file into new Imagick Object.
|
||||
*
|
||||
* @return true|WP_Error True if loaded; WP_Error on failure.
|
||||
*/
|
||||
public function load() {
|
||||
if ( $this->image instanceof Imagick ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) {
|
||||
return new WP_Error( 'error_loading_image', __( 'File doesn’t exist?' ), $this->file );
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( strpos( $this->file, $upload_dir['basedir'] ) !== 0 ) {
|
||||
return parent::load();
|
||||
}
|
||||
|
||||
$temp_filename = tempnam( get_temp_dir(), 'infinite-uploads' );
|
||||
$this->temp_files_to_cleanup[] = $temp_filename;
|
||||
|
||||
copy( $this->file, $temp_filename );
|
||||
$this->remote_filename = $this->file;
|
||||
$this->file = $temp_filename;
|
||||
|
||||
$result = parent::load();
|
||||
|
||||
$this->file = $this->remote_filename;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imagick by default can't handle iu:// paths
|
||||
* for saving images. We have instead save it to a file file,
|
||||
* then copy it to the iu:// path as a workaround.
|
||||
*/
|
||||
protected function _save( $image, $filename = null, $mime_type = null ) {
|
||||
list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
|
||||
|
||||
if ( ! $filename ) {
|
||||
$filename = $this->generate_filename( null, null, $extension );
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( strpos( $filename, $upload_dir['basedir'] ) === 0 ) {
|
||||
$temp_filename = tempnam( get_temp_dir(), 'infinite-uploads' );
|
||||
}
|
||||
|
||||
$save = parent::_save( $image, $temp_filename, $mime_type );
|
||||
|
||||
if ( is_wp_error( $save ) ) {
|
||||
unlink( $temp_filename );
|
||||
return $save;
|
||||
}
|
||||
|
||||
$copy_result = copy( $save['path'], $filename );
|
||||
|
||||
unlink( $save['path'] );
|
||||
unlink( $temp_filename );
|
||||
|
||||
if ( ! $copy_result ) {
|
||||
return new WP_Error( 'unable-to-copy-to-s3', 'Unable to copy the temp image to the cloud' );
|
||||
}
|
||||
|
||||
return [
|
||||
'path' => $filename,
|
||||
'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
|
||||
'width' => $this->size['width'],
|
||||
'height' => $this->size['height'],
|
||||
'mime-type' => $mime_type,
|
||||
];
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
array_map( 'unlink', $this->temp_files_to_cleanup );
|
||||
parent::__destruct();
|
||||
}
|
||||
}
|
@ -1,522 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Local streamwrapper that writes files to the upload dir
|
||||
*
|
||||
* This is for the most part taken from Drupal, with some modifications.
|
||||
*/
|
||||
class Infinite_Uploads_Local_Stream_Wrapper {
|
||||
/**
|
||||
* Stream context resource.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* A generic resource handle.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $handle = null;
|
||||
|
||||
/**
|
||||
* Instance URI (stream).
|
||||
*
|
||||
* A stream is referenced as "scheme://target".
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* Gets the path that the wrapper is responsible for.
|
||||
*
|
||||
* @return string
|
||||
* String specifying the path.
|
||||
*/
|
||||
static function getDirectoryPath() {
|
||||
$upload_dir = Infinite_Uploads::get_instance()->get_original_upload_dir();
|
||||
|
||||
return $upload_dir['basedir'] . '/iu';
|
||||
}
|
||||
|
||||
function setUri( $uri ) {
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
function getUri() {
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local writable target of the resource within the stream.
|
||||
*
|
||||
* This function should be used in place of calls to realpath() or similar
|
||||
* functions when attempting to determine the location of a file. While
|
||||
* functions like realpath() may return the location of a read-only file, this
|
||||
* method may return a URI or path suitable for writing that is completely
|
||||
* separate from the URI used for reading.
|
||||
*
|
||||
* @param string $uri
|
||||
* Optional URI.
|
||||
*
|
||||
* @return string|bool
|
||||
* Returns a string representing a location suitable for writing of a file,
|
||||
* or FALSE if unable to write to the file such as with read-only streams.
|
||||
*/
|
||||
protected function getTarget( $uri = null ) {
|
||||
if ( ! isset( $uri ) ) {
|
||||
$uri = $this->uri;
|
||||
}
|
||||
|
||||
list( $scheme, $target) = explode( '://', $uri, 2 );
|
||||
|
||||
// Remove erroneous leading or trailing, forward-slashes and backslashes.
|
||||
return trim( $target, '\/' );
|
||||
}
|
||||
|
||||
static function getMimeType( $uri, $mapping = null ) {
|
||||
|
||||
$extension = '';
|
||||
$file_parts = explode( '.', basename( $uri ) );
|
||||
|
||||
// Remove the first part: a full filename should not match an extension.
|
||||
array_shift( $file_parts );
|
||||
|
||||
// Iterate over the file parts, trying to find a match.
|
||||
// For my.awesome.image.jpeg, we try:
|
||||
// - jpeg
|
||||
// - image.jpeg, and
|
||||
// - awesome.image.jpeg
|
||||
while ( $additional_part = array_pop( $file_parts ) ) {
|
||||
$extension = strtolower( $additional_part . ( $extension ? '.' . $extension : '' ) );
|
||||
if ( isset( $mapping['extensions'][ $extension ] ) ) {
|
||||
return $mapping['mimetypes'][ $mapping['extensions'][ $extension ] ];
|
||||
}
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
function chmod( $mode ) {
|
||||
$output = @chmod( $this->getLocalPath(), $mode );
|
||||
// We are modifying the underlying file here, so we have to clear the stat
|
||||
// cache so that PHP understands that URI has changed too.
|
||||
clearstatcache( true, $this->getLocalPath() );
|
||||
return $output;
|
||||
}
|
||||
|
||||
function realpath() {
|
||||
return $this->getLocalPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical absolute path of the URI, if possible.
|
||||
*
|
||||
* @param string $uri
|
||||
* (optional) The stream wrapper URI to be converted to a canonical
|
||||
* absolute path. This may point to a directory or another type of file.
|
||||
*
|
||||
* @return string|bool
|
||||
* If $uri is not set, returns the canonical absolute path of the URI
|
||||
* previously. If $uri is set and valid for this class, returns its canonical absolute
|
||||
* path, as determined by the realpath() function. If $uri is set but not
|
||||
* valid, returns FALSE.
|
||||
*/
|
||||
protected function getLocalPath( $uri = null ) {
|
||||
if ( ! isset( $uri ) ) {
|
||||
$uri = $this->uri;
|
||||
}
|
||||
$path = $this->getDirectoryPath() . '/' . $this->getTarget( $uri );
|
||||
$realpath = $path;
|
||||
|
||||
$directory = realpath( $this->getDirectoryPath() );
|
||||
|
||||
if ( ! $realpath || ! $directory || strpos( $realpath, $directory ) !== 0 ) {
|
||||
return false;
|
||||
}
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fopen(), file_get_contents(), file_put_contents() etc.
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the file to open.
|
||||
* @param int $mode
|
||||
* The file mode ("r", "wb" etc.).
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
|
||||
* @param string $opened_path
|
||||
* A string containing the path actually opened.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if file was opened successfully.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-open.php
|
||||
*/
|
||||
public function stream_open( $uri, $mode, $options, &$opened_path ) {
|
||||
$this->uri = $uri;
|
||||
$path = $this->getLocalPath();
|
||||
$this->handle = ( $options & STREAM_REPORT_ERRORS ) ? fopen( $path, $mode ) : @fopen( $path, $mode );
|
||||
|
||||
if ( (bool) $this->handle && $options & STREAM_USE_PATH ) {
|
||||
$opened_path = $path;
|
||||
}
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for chmod(), chown(), etc.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE on success or FALSE on failure.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-metadata.php
|
||||
*/
|
||||
public function stream_metadata() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for flock().
|
||||
*
|
||||
* @param int $operation
|
||||
* One of the following:
|
||||
* - LOCK_SH to acquire a shared lock (reader).
|
||||
* - LOCK_EX to acquire an exclusive lock (writer).
|
||||
* - LOCK_UN to release a lock (shared or exclusive).
|
||||
* - LOCK_NB if you don't want flock() to block while locking (not
|
||||
* supported on Windows).
|
||||
*
|
||||
* @return bool
|
||||
* Always returns TRUE at the present time.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-lock.php
|
||||
*/
|
||||
public function stream_lock( $operation ) {
|
||||
if ( in_array( $operation, [ LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB ] ) ) {
|
||||
return flock( $this->handle, $operation );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fread(), file_get_contents() etc.
|
||||
*
|
||||
* @param int $count
|
||||
* Maximum number of bytes to be read.
|
||||
*
|
||||
* @return string|bool
|
||||
* The string that was read, or FALSE in case of an error.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-read.php
|
||||
*/
|
||||
public function stream_read( $count ) {
|
||||
return fread( $this->handle, $count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fwrite(), file_put_contents() etc.
|
||||
*
|
||||
* @param string $data
|
||||
* The string to be written.
|
||||
*
|
||||
* @return int
|
||||
* The number of bytes written.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-write.php
|
||||
*/
|
||||
public function stream_write( $data ) {
|
||||
return fwrite( $this->handle, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for feof().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if end-of-file has been reached.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-eof.php
|
||||
*/
|
||||
public function stream_eof() {
|
||||
return feof( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fseek().
|
||||
*
|
||||
* @param int $offset
|
||||
* The byte offset to got to.
|
||||
* @param int $whence
|
||||
* SEEK_SET, SEEK_CUR, or SEEK_END.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-seek.php
|
||||
*/
|
||||
public function stream_seek( $offset, $whence ) {
|
||||
// fseek returns 0 on success and -1 on a failure.
|
||||
// stream_seek 1 on success and 0 on a failure.
|
||||
return ! fseek( $this->handle, $offset, $whence );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fflush().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if data was successfully stored (or there was no data to store).
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-flush.php
|
||||
*/
|
||||
public function stream_flush() {
|
||||
$result = fflush( $this->handle );
|
||||
|
||||
$params = [
|
||||
'Bucket' => Infinite_Uploads::get_instance()->bucket,
|
||||
'Key' => trim( str_replace( Infinite_Uploads::get_instance()->bucket, '', $this->getTarget() ), '/' ),
|
||||
];
|
||||
|
||||
/**
|
||||
* Action when a new object has been uploaded to b2.
|
||||
*
|
||||
* @param array $params S3Client::putObject parameters.
|
||||
*/
|
||||
do_action( 'infinite_uploads_putObject', $params );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for ftell().
|
||||
*
|
||||
* @return bool
|
||||
* The current offset in bytes from the beginning of file.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-tell.php
|
||||
*/
|
||||
public function stream_tell() {
|
||||
return ftell( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fstat().
|
||||
*
|
||||
* @return bool
|
||||
* An array with file status, or FALSE in case of an error - see fstat()
|
||||
* for a description of this array.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-stat.php
|
||||
*/
|
||||
public function stream_stat() {
|
||||
return fstat( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fclose().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if stream was successfully closed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-close.php
|
||||
*/
|
||||
public function stream_close() {
|
||||
return fclose( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying stream resource for stream_select().
|
||||
*
|
||||
* @param int $cast_as
|
||||
* Can be STREAM_CAST_FOR_SELECT or STREAM_CAST_AS_STREAM.
|
||||
*
|
||||
* @return resource|false
|
||||
* The underlying stream resource or FALSE if stream_select() is not
|
||||
* supported.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-cast.php
|
||||
*/
|
||||
public function stream_cast( $cast_as ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for unlink().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the resource to delete.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if resource was successfully deleted.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.unlink.php
|
||||
*/
|
||||
public function unlink( $uri ) {
|
||||
$this->uri = $uri;
|
||||
return unlink( $this->getLocalPath() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rename().
|
||||
*
|
||||
* @param string $from_uri,
|
||||
* The URI to the file to rename.
|
||||
* @param string $to_uri
|
||||
* The new URI for file.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if file was successfully renamed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.rename.php
|
||||
*/
|
||||
public function rename( $from_uri, $to_uri ) {
|
||||
return rename( $this->getLocalPath( $from_uri ), $this->getLocalPath( $to_uri ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for mkdir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to create.
|
||||
* @param int $mode
|
||||
* Permission flags - see mkdir().
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if directory was successfully created.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.mkdir.php
|
||||
*/
|
||||
public function mkdir( $uri, $mode, $options ) {
|
||||
$this->uri = $uri;
|
||||
$recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
|
||||
if ( $recursive ) {
|
||||
// $this->getLocalPath() fails if $uri has multiple levels of directories
|
||||
// that do not yet exist.
|
||||
$localpath = $this->getDirectoryPath() . '/' . $this->getTarget( $uri );
|
||||
} else {
|
||||
$localpath = $this->getLocalPath( $uri );
|
||||
}
|
||||
if ( $options & STREAM_REPORT_ERRORS ) {
|
||||
return mkdir( $localpath, $mode, $recursive );
|
||||
} else {
|
||||
return @mkdir( $localpath, $mode, $recursive );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rmdir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to delete.
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_REPORT_ERRORS.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if directory was successfully removed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.rmdir.php
|
||||
*/
|
||||
public function rmdir( $uri, $options ) {
|
||||
$this->uri = $uri;
|
||||
if ( $options & STREAM_REPORT_ERRORS ) {
|
||||
return rmdir( $this->getLocalPath() );
|
||||
} else {
|
||||
return @rmdir( $this->getLocalPath() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for stat().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to get information about.
|
||||
* @param int $flags
|
||||
* A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
|
||||
*
|
||||
* @return array
|
||||
* An array with file status, or FALSE in case of an error - see fstat()
|
||||
* for a description of this array.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.url-stat.php
|
||||
*/
|
||||
public function url_stat( $uri, $flags ) {
|
||||
$this->uri = $uri;
|
||||
$path = $this->getLocalPath();
|
||||
// Suppress warnings if requested or if the file or directory does not
|
||||
// exist. This is consistent with PHP's plain filesystem stream wrapper.
|
||||
if ( $flags & STREAM_URL_STAT_QUIET || ! file_exists( $path ) ) {
|
||||
return @stat( $path );
|
||||
} else {
|
||||
return stat( $path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for opendir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to open.
|
||||
* @param int $options
|
||||
* Unknown (parameter is not documented in PHP Manual).
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-opendir.php
|
||||
*/
|
||||
public function dir_opendir( $uri, $options ) {
|
||||
$this->uri = $uri;
|
||||
$this->handle = opendir( $this->getLocalPath() );
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for readdir().
|
||||
*
|
||||
* @return string
|
||||
* The next filename, or FALSE if there are no more files in the directory.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-readdir.php
|
||||
*/
|
||||
public function dir_readdir() {
|
||||
return readdir( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rewinddir().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-rewinddir.php
|
||||
*/
|
||||
public function dir_rewinddir() {
|
||||
rewinddir( $this->handle );
|
||||
// We do not really have a way to signal a failure as rewinddir() does not
|
||||
// have a return value and there is no way to read a directory handler
|
||||
// without advancing to the next file.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for closedir().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-closedir.php
|
||||
*/
|
||||
public function dir_closedir() {
|
||||
closedir( $this->handle );
|
||||
// We do not really have a way to signal a failure as closedir() does not
|
||||
// have a return value.
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Infinite_Uploads_Rewriter
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
class Infinite_Uploads_Rewriter {
|
||||
protected $uploads_path = null; // uploads PATH
|
||||
protected $replacements = []; // urls to be searched for and replaced with CDN URL
|
||||
protected $cdn_url = null; // CDN URL
|
||||
protected $exclusions = [];
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param string $uploads_url Original upload url
|
||||
* @param array $replacements Urls to filter
|
||||
* @param string $cdn_url Destination CDN url
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
function __construct( $uploads_url, $replacements, $cdn_url ) {
|
||||
|
||||
$this->uploads_path = trailingslashit( parse_url( $uploads_url, PHP_URL_PATH ) );
|
||||
|
||||
$this->replacements = array_unique(
|
||||
array_map( [ $this, 'protocolize_url' ],
|
||||
apply_filters( 'infinite_uploads_replacement_urls', $replacements )
|
||||
)
|
||||
);
|
||||
|
||||
$this->cdn_url = $this->protocolize_url( $cdn_url );
|
||||
|
||||
//generate upload url paths that should be excluded from url replacement
|
||||
$filelist = new Infinite_Uploads_Filelist( '/' ); //path doesn't matter
|
||||
$exclusions = apply_filters( 'infinite_uploads_sync_exclusions', $filelist->exclusions );
|
||||
foreach ( $exclusions as $exclusion ) {
|
||||
if ( 0 === strpos( $exclusion, '/' ) ) {
|
||||
$this->exclusions[ $exclusion ] = untrailingslashit( $uploads_url ) . $exclusion;
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'template_redirect', [ &$this, 'handle_rewrite_hook' ] );
|
||||
|
||||
// Make sure we replace urls in REST API responses
|
||||
add_filter( 'the_content', [ &$this, 'rewrite_the_content' ], 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add https protocol to url when needed
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public function protocolize_url( $url ) {
|
||||
if ( strpos( $url, ':' ) === false && ! in_array( $url[0], [ '/', '#', '?' ], true ) &&
|
||||
! preg_match( '/^[a-z0-9-]+?\.php/i', $url ) ) {
|
||||
$url = 'https://' . $url;
|
||||
}
|
||||
|
||||
return trailingslashit( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* run rewrite hook
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public function handle_rewrite_hook() {
|
||||
ob_start( [ &$this, 'rewrite' ] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* rewrite html content
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public function rewrite_the_content( $html ) {
|
||||
return $this->rewrite( $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* rewrite url
|
||||
*
|
||||
* @param string $html current raw HTML doc
|
||||
*
|
||||
* @return string updated HTML doc with CDN links
|
||||
* @since 1.0
|
||||
*
|
||||
*/
|
||||
public function rewrite( $html ) {
|
||||
|
||||
// start regex
|
||||
$regex_rule = '#((?:https?:)?(?:';
|
||||
|
||||
//add all the domains to replace
|
||||
$regex_rule .= implode( '|',
|
||||
array_map( [ $this, 'relative_url' ],
|
||||
array_map( 'quotemeta', $this->replacements )
|
||||
)
|
||||
);
|
||||
|
||||
// check for relative paths
|
||||
$regex_rule .= ')|(?<=[(\"\'=\s])' . quotemeta( $this->uploads_path ) . ')([^\#\"\'\s]*)#';
|
||||
|
||||
// call the cdn rewriter callback
|
||||
$cdn_html = preg_replace_callback( $regex_rule, [ $this, 'rewrite_url' ], $html );
|
||||
|
||||
return $cdn_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative url
|
||||
*
|
||||
* @param string $url a full url
|
||||
*
|
||||
* @return string protocol relative url
|
||||
* @since 1.0
|
||||
*
|
||||
*/
|
||||
protected function relative_url( $url ) {
|
||||
return substr( $url, strpos( $url, '//' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* rewrite url
|
||||
*
|
||||
* @param string $matches the matches from regex
|
||||
*
|
||||
* @return string updated url if not excluded
|
||||
* @since 1.0
|
||||
*
|
||||
*/
|
||||
protected function rewrite_url( $matches ) {
|
||||
|
||||
//don't filter excluded dirs
|
||||
foreach ( $this->exclusions as $exclusion ) {
|
||||
if ( 0 === strpos( $matches[0], $exclusion ) ) {
|
||||
return $matches[0];
|
||||
}
|
||||
}
|
||||
|
||||
$replace = str_replace( $matches[1], $this->cdn_url, $matches[0] );
|
||||
|
||||
/**
|
||||
* Filters the find/replace url rewriter that replaces matches in HTML output with CDN url.
|
||||
*
|
||||
* @param {string} $replace The url to replace the match with, like `https://xxxxx.infiniteuploads.cloud/somefile.png`.
|
||||
* @param {array} $matches The the matches found in HTML, like `[0 => 'https://mysite.com/wp-content/uploads/somefile.png', 1 => 'https://mysite.com/wp-content/uploads/', 2 => 'somefile.png']`.
|
||||
*
|
||||
* @return {string} The base url to replace the match with.
|
||||
* @since 1.0
|
||||
* @hook infinite_uploads_rewrite_url
|
||||
*
|
||||
*/
|
||||
return apply_filters( 'infinite_uploads_rewrite_url', $replace, $matches );
|
||||
}
|
||||
}
|
@ -1,611 +0,0 @@
|
||||
<?php
|
||||
|
||||
use UglyRobot\Infinite_Uploads\GuzzleHttp;
|
||||
|
||||
class Infinite_Uploads_Video {
|
||||
|
||||
private static $instance;
|
||||
private $iup_instance;
|
||||
private $api;
|
||||
|
||||
public function __construct() {
|
||||
$this->iup_instance = Infinite_Uploads::get_instance();
|
||||
$this->api = Infinite_Uploads_Api_Handler::get_instance();
|
||||
|
||||
/*
|
||||
//for testing, override values that our API would normally provide TODO remove
|
||||
add_filter( 'infinite_uploads_video_config', function ( $return, $key, $data ) {
|
||||
if ( 'library_id' === $key ) {
|
||||
return 56793;
|
||||
} elseif ( 'url' === $key ) {
|
||||
return 'https://vz-30d13541-113.b-cdn.net';
|
||||
} elseif ( 'key_read' === $key ) {
|
||||
return BUNNY_API_KEY;
|
||||
} elseif ( 'key_write' === $key ) {
|
||||
return BUNNY_API_KEY;
|
||||
} elseif ( 'enabled' === $key ) {
|
||||
return defined( 'BUNNY_API_KEY' );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}, 10, 3 );
|
||||
*/
|
||||
|
||||
add_action( 'wp_ajax_infinite-uploads-video-activate', [ &$this, 'ajax_activate_video' ] );
|
||||
|
||||
if ( $this->is_video_active() ) {
|
||||
add_action( 'admin_menu', [ &$this, 'admin_menu' ], 20 );
|
||||
add_action( 'network_admin_menu', [ &$this, 'admin_menu' ], 20 );
|
||||
|
||||
//all write API calls we make on backend to not expose write API key
|
||||
add_action( 'wp_ajax_infinite-uploads-video-create', [ &$this, 'ajax_create_video' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-video-update', [ &$this, 'ajax_update_video' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-video-delete', [ &$this, 'ajax_delete_video' ] );
|
||||
add_action( 'wp_ajax_infinite-uploads-video-settings', [ &$this, 'ajax_update_settings' ] );
|
||||
|
||||
//gutenberg block
|
||||
add_action( 'init', [ &$this, 'register_block' ] );
|
||||
add_action( 'enqueue_block_editor_assets', [ &$this, 'script_enqueue' ] );
|
||||
}
|
||||
|
||||
//shortcode
|
||||
add_shortcode( 'infinite-uploads-vid', [ &$this, 'shortcode' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Infinite_Uploads_Video
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new Infinite_Uploads_Video();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if Video is created/active for this site.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_video_active() {
|
||||
return (bool) $this->get_config( 'library_id' );
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if Video uploading/viewing is enabled. (When user has billing issues,
|
||||
* we will disable embeds and return only the read_key for viewing the library but no editing)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_video_enabled() {
|
||||
return $this->is_video_active() && $this->get_config( 'enabled' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates Video service for this site.
|
||||
*
|
||||
* @return object|false
|
||||
*/
|
||||
public function activate_video() {
|
||||
$result = $this->api->call( "site/" . $this->api->get_site_id() . "/video", [], 'POST' );
|
||||
if ( $result ) {
|
||||
//cache the new creds/settings once we enable video
|
||||
return $this->get_library_settings( true );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the video library settings.
|
||||
*
|
||||
* @return object|false
|
||||
*/
|
||||
public function update_library_settings( $args = [] ) {
|
||||
$new_settings = $this->api->call( "site/" . $this->api->get_site_id() . "/video", $args, 'POST' );
|
||||
if ( $new_settings ) {
|
||||
//TODO don't make another api call, just update the cache
|
||||
return $this->get_library_settings( true );
|
||||
}
|
||||
|
||||
return $new_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the video library settings. They are cached 12hrs in the options table from the regular get_site_data call by default.
|
||||
*
|
||||
* @param bool $force_refresh Force a refresh of the settings from api.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
public function get_library_settings( $force_refresh = false ) {
|
||||
$data = $this->api->get_site_data( $force_refresh );
|
||||
|
||||
if ( isset( $data->video->settings ) ) {
|
||||
return $data->video->settings;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the video configuration value for a given key. They are cached 12hrs in the options table from the regular get_site_data call by default.
|
||||
*
|
||||
* @param string $key The key to get (enabled, library_id, key_write, key_read, url).
|
||||
* @param bool $force_refresh Force a refresh of the credentials.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_config( $key, $force_refresh = false ) {
|
||||
$data = $this->api->get_site_data( $force_refresh );
|
||||
if ( $override = apply_filters( 'infinite_uploads_video_config', null, $key, $data ) ) {
|
||||
return $override;
|
||||
}
|
||||
if ( isset( $data->video->{$key} ) ) {
|
||||
return $data->video->{$key};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an API request to Bunny Video API.
|
||||
*
|
||||
* @param string $path API path.
|
||||
* @param array $data Data array.
|
||||
* @param string $method Method. Default: POST.
|
||||
*
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
private function api_call( $path, $data = [], $method = 'POST' ) {
|
||||
$library_id = $this->get_config( 'library_id' );
|
||||
|
||||
$url = "https://video.bunnycdn.com/library/{$library_id}/" . ltrim( $path, '/' );
|
||||
|
||||
$headers = array(
|
||||
'Accept' => 'application/json',
|
||||
'AccessKey' => $this->get_config( 'key_write' ),
|
||||
'Content-Type' => 'application/json',
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'headers' => $headers,
|
||||
'sslverify' => true,
|
||||
'method' => strtoupper( $method ),
|
||||
'timeout' => 30,
|
||||
);
|
||||
|
||||
switch ( strtolower( $method ) ) {
|
||||
case 'post':
|
||||
$args['body'] = wp_json_encode( $data );
|
||||
|
||||
$response = wp_remote_post( $url, $args );
|
||||
break;
|
||||
case 'post_file':
|
||||
$args['body'] = $data;
|
||||
$args['headers']['Content-Type'] = 'application/binary';
|
||||
$args['method'] = 'POST';
|
||||
|
||||
$response = wp_remote_post( $url, $args );
|
||||
break;
|
||||
case 'get':
|
||||
if ( ! empty( $data ) ) {
|
||||
$url = add_query_arg( $data, $url );
|
||||
}
|
||||
|
||||
$response = wp_remote_get( $url, $args );
|
||||
break;
|
||||
default:
|
||||
if ( ! empty( $data ) ) {
|
||||
$args['body'] = wp_json_encode( $data );
|
||||
}
|
||||
$response = wp_remote_request( $url, $args );
|
||||
break;
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ) );
|
||||
|
||||
if ( ! in_array( wp_remote_retrieve_response_code( $response ), [ 200, 201, 202, 204, 204 ], true ) ) {
|
||||
error_log( "Bunny API error: " . wp_remote_retrieve_response_code( $response ) . " " . wp_remote_retrieve_body( $response ) );
|
||||
|
||||
if ( isset( $body->ErrorKey ) ) {
|
||||
return new WP_Error( $body->ErrorKey, $body->Message, [ 'status' => wp_remote_retrieve_response_code( $response ) ] );
|
||||
} else {
|
||||
return new WP_Error( 'bunny_api_error', wp_remote_retrieve_response_message( $response ), [ 'status' => wp_remote_retrieve_response_code( $response ) ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the block's assets for the editor.
|
||||
*
|
||||
* @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/
|
||||
*/
|
||||
function script_enqueue() {
|
||||
$data = array(
|
||||
'libraryId' => $this->get_config( 'library_id' ),
|
||||
'cdnUrl' => 'https://' . $this->get_config( 'url' ), // This give us the base CDN url for the library for building media links.
|
||||
'apiKey' => $this->get_config( 'key_write' ), //we only expose the read key to the frontend. The write key is only used via backend ajax wrappers.
|
||||
'settings' => $this->get_library_settings(),
|
||||
'nonce' => wp_create_nonce( 'iup_video' ), //used to verify the request is coming from the frontend, CSRF.
|
||||
'assetBase' => plugins_url( 'assets', __FILE__ ),
|
||||
'settingsUrl' => $this->settings_url(),
|
||||
'libraryUrl' => $this->library_url(),
|
||||
);
|
||||
wp_register_script( 'iup-dummy-js-header', '' );
|
||||
wp_enqueue_script( 'iup-dummy-js-header' );
|
||||
wp_add_inline_script( 'iup-dummy-js-header', 'const IUP_VIDEO = ' . json_encode( $data ) . ';' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check permissions for a ajax request.
|
||||
*
|
||||
* @param $nonce
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_check_permissions( $nonce = 'iup_video' ) {
|
||||
// check caps
|
||||
if ( ! current_user_can( 'upload_files' ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
//check nonce
|
||||
if ( ! check_ajax_referer( $nonce, 'nonce', false ) ) {
|
||||
wp_send_json_error( esc_html__( 'Permissions Error: Please refresh the page and try again.', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
// return error if video is not enabled
|
||||
if ( $this->is_video_active() && ! $this->is_video_enabled() ) {
|
||||
wp_send_json_error( esc_html__( 'Infinite Uploads Video is disabled due to an issue with your account.', 'infinite-uploads' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a video in the video library, and returns the params for executing the tus upload.
|
||||
*
|
||||
* @see https://docs.bunny.net/reference/video_createvideo
|
||||
* @see https://docs.bunny.net/reference/tus-resumable-uploads
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_create_video() {
|
||||
$this->ajax_check_permissions();
|
||||
|
||||
$result = $this->api_call( '/videos', [ 'title' => sanitize_text_field( wp_unslash( $_REQUEST['title'] ) ) ] );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
|
||||
//generate the signature params for a tus upload.
|
||||
$expiration = time() + ( 6 * HOUR_IN_SECONDS );
|
||||
$response = [
|
||||
'AuthorizationSignature' => hash( 'sha256', $result->videoLibraryId . $this->get_config( 'key_write' ) . $expiration . $result->guid ), // SHA256 signature (library_id + api_key + expiration_time + video_id)
|
||||
'AuthorizationExpire' => $expiration, // Expiration time as in the signature,
|
||||
'VideoId' => $result->guid, // The guid of a previously created video object through the Create Video API call
|
||||
'LibraryId' => $result->videoLibraryId,
|
||||
];
|
||||
|
||||
wp_send_json_success( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a video in the video library.
|
||||
*
|
||||
* @see https://docs.bunny.net/reference/video_updatevideo
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_update_video() {
|
||||
$this->ajax_check_permissions();
|
||||
|
||||
$video_id = sanitize_text_field( $_REQUEST['video_id'] );
|
||||
|
||||
if ( isset( $_REQUEST['title'] ) ) {
|
||||
$title = sanitize_text_field( wp_unslash( $_REQUEST['title'] ) );
|
||||
$result = $this->api_call( "/videos/$video_id", compact( 'title' ) );
|
||||
} elseif ( isset( $_REQUEST['thumbnail'] ) ) {
|
||||
$thumbnail = sanitize_text_field( $_REQUEST['thumbnail'] );
|
||||
$result = $this->api_call( "/videos/$video_id/thumbnail?thumbnailUrl=" . $thumbnail );
|
||||
} elseif ( isset( $_FILES['thumbnailFile'] ) ) {
|
||||
$result = $this->api_call( "/videos/$video_id/thumbnail", file_get_contents( $_FILES['thumbnailFile']['tmp_name'] ), 'POST_FILE' );
|
||||
} else {
|
||||
wp_send_json_error( esc_html__( 'Invalid request', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
|
||||
wp_send_json_success( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a video in the video library.
|
||||
*
|
||||
* @see https://docs.bunny.net/reference/video_deletevideo
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_delete_video() {
|
||||
$this->ajax_check_permissions();
|
||||
|
||||
$video_id = sanitize_text_field( $_REQUEST['video_id'] );
|
||||
|
||||
$result = $this->api_call( "/videos/$video_id", [], 'DELETE' );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
|
||||
wp_send_json_success( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update video library settings via Infinite Uploads API.
|
||||
*
|
||||
* @see https://docs.bunny.net/reference/video_updatevideo
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_update_settings() {
|
||||
$this->ajax_check_permissions();
|
||||
|
||||
//this is proxied to the Infinite Uploads API, and is sanitized/validated there.
|
||||
$settings = json_decode( wp_unslash( $_REQUEST['settings'] ) );
|
||||
$result = $this->update_library_settings( $settings );
|
||||
|
||||
if ( ! $result ) {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
|
||||
wp_send_json_success( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update video library settings via Infinite Uploads API.
|
||||
*
|
||||
* @see https://docs.bunny.net/reference/video_updatevideo
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_activate_video() {
|
||||
$this->ajax_check_permissions();
|
||||
|
||||
$result = $this->activate_video();
|
||||
|
||||
if ( ! $result ) {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
|
||||
wp_send_json_success( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the video library page under Media.
|
||||
*/
|
||||
function admin_menu() {
|
||||
$page = add_media_page(
|
||||
__( 'Video Library - Infinite Uploads', 'infinite-uploads' ),
|
||||
__( 'Video Library', 'infinite-uploads' ),
|
||||
'upload_files',
|
||||
'infinite_uploads_vids',
|
||||
[
|
||||
$this,
|
||||
'library_page',
|
||||
],
|
||||
1.1678 //for unique menu position above Add New.
|
||||
);
|
||||
|
||||
add_action( 'admin_print_scripts-' . $page, [ &$this, 'script_enqueue' ] );
|
||||
add_action( 'admin_print_scripts-' . $page, [ &$this, 'admin_scripts' ] );
|
||||
add_action( 'admin_print_styles-' . $page, [ &$this, 'admin_styles' ] );
|
||||
|
||||
//video settings page.
|
||||
$page = add_submenu_page(
|
||||
'infinite_uploads',
|
||||
__( 'Infinite Uploads Video', 'infinite-uploads' ),
|
||||
__( 'Video Cloud', 'infinite-uploads' ),
|
||||
$this->iup_instance->capability,
|
||||
'infinite_uploads_video_settings',
|
||||
[
|
||||
$this,
|
||||
'settings_page',
|
||||
]
|
||||
);
|
||||
|
||||
add_action( 'admin_print_scripts-' . $page, [ &$this, 'script_enqueue' ] );
|
||||
add_action( 'admin_print_scripts-' . $page, [ &$this, 'admin_scripts' ] );
|
||||
add_action( 'admin_print_styles-' . $page, [ &$this, 'admin_styles' ] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the settings url with optional url args.
|
||||
*
|
||||
* @param array $args Optional. Same as for add_query_arg()
|
||||
*
|
||||
* @return string Unescaped url to settings page.
|
||||
*/
|
||||
function settings_url( $args = [] ) {
|
||||
if ( is_multisite() ) {
|
||||
$base = network_admin_url( 'admin.php?page=infinite_uploads_video_settings' );
|
||||
} else {
|
||||
$base = admin_url( 'admin.php?page=infinite_uploads_video_settings' );
|
||||
}
|
||||
|
||||
return add_query_arg( $args, $base );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings url with optional url args.
|
||||
*
|
||||
* @param array $args Optional. Same as for add_query_arg()
|
||||
*
|
||||
* @return string Unescaped url to settings page.
|
||||
*/
|
||||
function library_url( $args = [] ) {
|
||||
$base = admin_url( 'upload.php?page=infinite_uploads_vids' );
|
||||
|
||||
return add_query_arg( $args, $base );
|
||||
}
|
||||
|
||||
function register_block() {
|
||||
register_block_type( __DIR__ . '/video/block' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo adjust for the video library page.
|
||||
*/
|
||||
function admin_scripts() {
|
||||
wp_enqueue_script( 'iup-admin', plugins_url( 'build/admin.js', __DIR__ ), array( 'wp-element', 'wp-i18n' ), ( wp_get_environment_type() !== 'production' ? time() : INFINITE_UPLOADS_VERSION ), false );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function admin_styles() {
|
||||
wp_enqueue_style( 'iup-uppy', plugins_url( 'build/style-block.css', __DIR__ ), false, INFINITE_UPLOADS_VERSION ); //Have no idea why webpack is putting uppy css in this file.
|
||||
wp_enqueue_style( 'iup-admin', plugins_url( 'build/admin.css', __DIR__ ), false, INFINITE_UPLOADS_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Video library page display callback.
|
||||
*/
|
||||
function library_page() {
|
||||
?>
|
||||
<div id="iup-videos-page" class="wrap iup-background">
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once( dirname( __FILE__ ) . '/templates/footer.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Video library page display callback.
|
||||
*/
|
||||
function settings_page() {
|
||||
?>
|
||||
<div id="iup-video-settings-page" class="wrap iup-background">
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once( dirname( __FILE__ ) . '/templates/footer.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Video embed shortcode.
|
||||
*/
|
||||
function shortcode( $atts ) {
|
||||
//hide shortcode when not logged in.
|
||||
if ( ! $this->is_video_active() ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$atts = shortcode_atts(
|
||||
[
|
||||
'id' => '',
|
||||
'autoplay' => 'false',
|
||||
'loop' => 'false',
|
||||
'muted' => 'false',
|
||||
'preload' => 'true',
|
||||
],
|
||||
$atts,
|
||||
'infinite-uploads-vid'
|
||||
);
|
||||
|
||||
// Force preload to always be "true"
|
||||
$atts['preload'] = 'true';
|
||||
|
||||
$video_url = esc_url_raw( sprintf( 'https://iframe.mediadelivery.net/embed/%d/%s', $this->get_config( 'library_id' ), $atts['id'] ) );
|
||||
|
||||
unset( $atts['id'] );
|
||||
|
||||
// Ensure autoplay is always set to "false" if not explicitly defined
|
||||
if ( ! isset( $atts['autoplay'] ) ) {
|
||||
$atts['autoplay'] = 'false';
|
||||
}
|
||||
|
||||
$video_url = add_query_arg(
|
||||
$atts,
|
||||
$video_url
|
||||
);
|
||||
|
||||
//fully escape now
|
||||
$video_url = esc_url( $video_url );
|
||||
|
||||
return <<<HTML
|
||||
<figure class="wp-block-video">
|
||||
<div style="position: relative;padding-top: 56.25%;"><iframe src="$video_url" loading="lazy" style="border: none; position: absolute; top: 0; height: 100%; width: 100%;" allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;" allowfullscreen="true"></iframe></div>
|
||||
</figure>
|
||||
HTML;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Video library page display callback.
|
||||
*
|
||||
* @todo This should be adapted to all bootstrap-react in it's own template files loaded by admin_scripts().
|
||||
*/
|
||||
function video_library_page2() {
|
||||
?>
|
||||
<div id="container" class="wrap iup-background">
|
||||
|
||||
<h1 class="text-muted mb-3">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/iu-logo-gray.svg', __FILE__ ) ); ?>" alt="Infinite Uploads Logo" height="36" width="36"/>
|
||||
<?php esc_html_e( 'Video Library', 'infinite-uploads' ); ?>
|
||||
</h1>
|
||||
|
||||
<div id="iup-error" class="alert alert-danger mt-1" role="alert"></div>
|
||||
|
||||
<?php if ( isset( $api_data->site ) && ! $api_data->site->cdn_enabled ) { ?>
|
||||
<div class="alert alert-warning mt-1" role="alert">
|
||||
<?php printf( __( "Files can't be uploaded and your CDN is disabled due to a billing issue with your Infinite Uploads account. Please <a href='%s' class='alert-link'>visit your account page</a> to fix, or disconnect this site from the cloud. Images and links to media on your site may be broken until you take action. <a href='%s' class='alert-link' data-toggle='tooltip' title='Refresh account data'>Already fixed?</a>", 'infinite-uploads' ), esc_url( $this->api_url( '/account/billing/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin' ) ), esc_url( $this->settings_url( [ 'refresh' => 1 ] ) ) ); ?>
|
||||
</div>
|
||||
<?php } elseif ( isset( $api_data->site ) && ! $api_data->site->upload_writeable ) { ?>
|
||||
<div class="alert alert-warning mt-1" role="alert">
|
||||
<?php printf( __( "Files can't be uploaded and your CDN will be disabled soon due to a billing issue with your Infinite Uploads account. Please <a href='%s' class='alert-link'>visit your account page</a> to fix, or disconnect this site from the cloud. <a href='%s' class='alert-link' data-toggle='tooltip' title='Refresh account data'>Already fixed?</a>", 'infinite-uploads' ), esc_url( $this->api_url( '/account/billing/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin' ) ), esc_url( $this->settings_url( [ 'refresh' => 1 ] ) ) ); ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<!-- Example Upload modal -->
|
||||
<div class="modal fade" id="upload-modal" tabindex="-1" role="dialog" aria-labelledby="upload-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="upload-modal-label"><?php esc_html_e( 'Upload Video', 'infinite-uploads' ); ?></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center mb-4 mt-3">
|
||||
<div class="col text-center">
|
||||
<h4><?php esc_html_e( 'Add a New Video to the Infinite Uploads Cloud', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( 'Drop your videos here to upload them to your cloud video library for encoding and embedding on your site.', 'infinite-uploads' ); ?></p>
|
||||
|
||||
<img class="mb-4" src="<?php echo esc_url( plugins_url( '/inc/assets/img/push-to-cloud.svg', dirname( __FILE__ ) ) ); ?>" alt="Upload to Cloud" height="76" width="76"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
require_once( dirname( __FILE__ ) . '/templates/footer.php' );
|
||||
}
|
||||
}
|
@ -1,656 +0,0 @@
|
||||
<?php
|
||||
|
||||
use UglyRobot\Infinite_Uploads\Aws\S3\Transfer;
|
||||
use UglyRobot\Infinite_Uploads\Aws\Exception\AwsException;
|
||||
use UglyRobot\Infinite_Uploads\Aws\Exception\S3Exception;
|
||||
use UglyRobot\Infinite_Uploads\Aws\Middleware;
|
||||
use UglyRobot\Infinite_Uploads\Aws\ResultInterface;
|
||||
|
||||
class Infinite_Uploads_WP_CLI_Command extends WP_CLI_Command {
|
||||
|
||||
/**
|
||||
* Verifies the site is connected and uploads and downloads from the Infinite Uploads cloud are working.
|
||||
*
|
||||
* @subcommand verify
|
||||
*/
|
||||
public function verify_api_keys() {
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get S3 Upload instance.
|
||||
$instance = Infinite_Uploads::get_instance();
|
||||
|
||||
// Create a path in the base directory, with a random file name to avoid potentially overwriting existing data.
|
||||
$upload_dir = wp_upload_dir();
|
||||
$s3_path = $upload_dir['basedir'] . '/' . mt_rand() . '.txt';
|
||||
|
||||
// Attempt to copy the local Canola test file to the generated path on Infinite Uploads cloud.
|
||||
WP_CLI::print_value( 'Attempting to upload file ' . $s3_path );
|
||||
|
||||
$copy = copy(
|
||||
dirname( dirname( __FILE__ ) ) . '/readme.txt',
|
||||
$s3_path
|
||||
);
|
||||
|
||||
// Check that the copy worked.
|
||||
if ( ! $copy ) {
|
||||
WP_CLI::error( 'Failed to copy / write to Infinite Uploads cloud - check your policy?' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WP_CLI::print_value( 'File uploaded to Infinite Uploads cloud successfully.' );
|
||||
|
||||
// Delete the file off Infinite Uploads cloud.
|
||||
WP_CLI::print_value( 'Attempting to delete file. ' . $s3_path );
|
||||
$delete = unlink( $s3_path );
|
||||
|
||||
// Check that the delete worked.
|
||||
if ( ! $delete ) {
|
||||
WP_CLI::error( 'Failed to delete ' . $s3_path );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WP_CLI::print_value( 'File deleted from Infinite Uploads cloud successfully.' );
|
||||
|
||||
WP_CLI::success( 'Looks like your configuration is correct!' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the required constants for the Infinite Uploads cloud connections are set.
|
||||
*
|
||||
* @return bool true if all constants are set, else false.
|
||||
*/
|
||||
private function verify_s3_access_constants() {
|
||||
if ( ! Infinite_Uploads::get_instance()->bucket ) {
|
||||
WP_CLI::error( sprintf( 'This site is not yet connected to the Infinite Uploads cloud. Please connect using the settings page: %s', Infinite_Uploads_Admin::get_instance()->settings_url() ), false );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* List the files in the Infinite Uploads cloud. Optionally filter to the provided path.
|
||||
*
|
||||
* @synopsis [<path>]
|
||||
*/
|
||||
public function ls( $args ) {
|
||||
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$s3 = Infinite_Uploads::get_instance()->s3();
|
||||
|
||||
$prefix = '';
|
||||
|
||||
if ( strpos( Infinite_Uploads::get_instance()->bucket, '/' ) ) {
|
||||
$prefix = trailingslashit( str_replace( strtok( Infinite_Uploads::get_instance()->bucket, '/' ) . '/', '', Infinite_Uploads::get_instance()->bucket ) );
|
||||
}
|
||||
|
||||
if ( isset( $args[0] ) ) {
|
||||
$prefix .= trailingslashit( ltrim( $args[0], '/' ) );
|
||||
}
|
||||
|
||||
try {
|
||||
$objects = $s3->getIterator( 'ListObjects', [
|
||||
'Bucket' => strtok( Infinite_Uploads::get_instance()->bucket, '/' ),
|
||||
'Prefix' => $prefix,
|
||||
] );
|
||||
foreach ( $objects as $object ) {
|
||||
WP_CLI::line( str_replace( $prefix, '', $object['Key'] ) . "\t" . size_format( $object['Size'] ) . "\t" . $object['LastModified']->__toString() );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy files to / from the uploads directory. Use iu://bucket/location for Infinite Uploads cloud
|
||||
*
|
||||
* @synopsis <from> <to>
|
||||
*/
|
||||
private function cp( $args ) {
|
||||
|
||||
$from = $args[0];
|
||||
$to = $args[1];
|
||||
|
||||
if ( is_dir( $from ) ) {
|
||||
$this->recurse_copy( $from, $to );
|
||||
} else {
|
||||
copy( $from, $to );
|
||||
}
|
||||
|
||||
WP_CLI::success( sprintf( 'Completed copy from %s to %s', $from, $to ) );
|
||||
}
|
||||
|
||||
private function recurse_copy( $src, $dst ) {
|
||||
$dir = opendir( $src );
|
||||
@mkdir( $dst );
|
||||
while ( false !== ( $file = readdir( $dir ) ) ) {
|
||||
if ( ( '.' !== $file ) && ( '..' !== $file ) ) {
|
||||
if ( is_dir( $src . '/' . $file ) ) {
|
||||
$this->recurse_copy( $src . '/' . $file, $dst . '/' . $file );
|
||||
} else {
|
||||
WP_CLI::line( sprintf( 'Copying from %s to %s', $src . '/' . $file, $dst . '/' . $file ) );
|
||||
copy( $src . '/' . $file, $dst . '/' . $file );
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir( $dir );
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a directory to Infinite Uploads cloud
|
||||
*
|
||||
* @subcommand upload-directory
|
||||
* @synopsis <from> [<to>] [--concurrency=<concurrency>] [--verbose]
|
||||
*/
|
||||
private function upload_directory( $args, $args_assoc ) {
|
||||
|
||||
$from = $args[0];
|
||||
$to = '';
|
||||
if ( isset( $args[1] ) ) {
|
||||
$to = $args[1];
|
||||
}
|
||||
|
||||
$s3 = Infinite_Uploads::get_instance()->s3();
|
||||
$args_assoc = wp_parse_args( $args_assoc, [ 'concurrency' => 20, 'verbose' => false ] );
|
||||
|
||||
$transfer_args = [
|
||||
'concurrency' => $args_assoc['concurrency'],
|
||||
'debug' => (bool) $args_assoc['verbose'],
|
||||
'before' => function ( UglyRobot\Infinite_Uploads\Aws\Command $command ) {
|
||||
if ( in_array( $command->getName(), [ 'PutObject', 'CreateMultipartUpload' ], true ) ) {
|
||||
/// Expires:
|
||||
if ( defined( 'INFINITE_UPLOADS_HTTP_EXPIRES' ) ) {
|
||||
$command['Expires'] = INFINITE_UPLOADS_HTTP_EXPIRES;
|
||||
}
|
||||
// Cache-Control:
|
||||
if ( defined( 'INFINITE_UPLOADS_HTTP_CACHE_CONTROL' ) ) {
|
||||
if ( is_numeric( INFINITE_UPLOADS_HTTP_CACHE_CONTROL ) ) {
|
||||
$command['CacheControl'] = 'max-age=' . INFINITE_UPLOADS_HTTP_CACHE_CONTROL;
|
||||
} else {
|
||||
$command['CacheControl'] = INFINITE_UPLOADS_HTTP_CACHE_CONTROL;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
try {
|
||||
$manager = new Transfer( $s3, $from, 's3://' . Infinite_Uploads::get_instance()->bucket . '/' . $to, $transfer_args );
|
||||
$manager->transfer();
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the uploads directory to Infinite Uploads cloud storage.
|
||||
*
|
||||
* @subcommand sync
|
||||
* @synopsis [--concurrency=<concurrency>] [--noscan] [--verbose]
|
||||
*/
|
||||
public function sync( $args, $args_assoc ) {
|
||||
global $wpdb;
|
||||
|
||||
$instance = Infinite_Uploads::get_instance();
|
||||
$args_assoc = wp_parse_args( $args_assoc, [ 'concurrency' => 20, 'noscan' => false, 'verbose' => false ] );
|
||||
|
||||
$path = $instance->get_original_upload_dir_root();
|
||||
|
||||
if ( ! $args_assoc['noscan'] ) {
|
||||
if ( ! $this->build_scan() ) {
|
||||
return;
|
||||
}
|
||||
$stats = $instance->get_sync_stats();
|
||||
WP_CLI::line( sprintf( esc_html__( '%s files (%s) remaining to be synced.', 'infinite-uploads' ), $stats['remaining_files'], $stats['remaining_size'] ) );
|
||||
} elseif ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$s3 = $instance->s3();
|
||||
|
||||
//begin transfer
|
||||
$synced = $wpdb->get_var( "SELECT count(*) AS files FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1" );
|
||||
$unsynced = $wpdb->get_var( "SELECT count(*) AS files FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0" );
|
||||
$progress_bar = null;
|
||||
if ( ! $args_assoc['verbose'] ) {
|
||||
$progress_bar = \WP_CLI\Utils\make_progress_bar( esc_html__( 'Copying to the cloud...', 'infinite-uploads' ), $synced + $unsynced );
|
||||
for ( $i = 0; $i < $synced; $i ++ ) {
|
||||
$progress_bar->tick();
|
||||
}
|
||||
}
|
||||
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
if ( ! $progress['sync_started'] ) {
|
||||
$progress['sync_started'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
|
||||
$uploaded = 0;
|
||||
$break = false;
|
||||
while ( ! $break ) {
|
||||
$to_sync = $wpdb->get_col( "SELECT file FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors < 3 LIMIT 1000" );
|
||||
//build full paths
|
||||
$to_sync_full = [];
|
||||
foreach ( $to_sync as $key => $file ) {
|
||||
$to_sync_full[] = $path['basedir'] . $file;
|
||||
}
|
||||
|
||||
$obj = new ArrayObject( $to_sync_full );
|
||||
$from = $obj->getIterator();
|
||||
|
||||
$transfer_args = [
|
||||
'concurrency' => $args_assoc['concurrency'],
|
||||
'base_dir' => $path['basedir'],
|
||||
'before' => function ( UglyRobot\Infinite_Uploads\Aws\Command $command ) use ( $args_assoc, $progress_bar, $wpdb, $unsynced, &$uploaded ) {
|
||||
if ( in_array( $command->getName(), [ 'PutObject', 'CreateMultipartUpload' ], true ) ) {
|
||||
/// Expires:
|
||||
if ( defined( 'INFINITE_UPLOADS_HTTP_EXPIRES' ) ) {
|
||||
$command['Expires'] = INFINITE_UPLOADS_HTTP_EXPIRES;
|
||||
}
|
||||
// Cache-Control:
|
||||
if ( defined( 'INFINITE_UPLOADS_HTTP_CACHE_CONTROL' ) ) {
|
||||
if ( is_numeric( INFINITE_UPLOADS_HTTP_CACHE_CONTROL ) ) {
|
||||
$command['CacheControl'] = 'max-age=' . INFINITE_UPLOADS_HTTP_CACHE_CONTROL;
|
||||
} else {
|
||||
$command['CacheControl'] = INFINITE_UPLOADS_HTTP_CACHE_CONTROL;
|
||||
}
|
||||
}
|
||||
}
|
||||
//add middleware to intercept result of each file upload
|
||||
if ( in_array( $command->getName(), [ 'PutObject', 'CompleteMultipartUpload' ], true ) ) {
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $args_assoc, $progress_bar, $command, $wpdb, $unsynced, &$uploaded ) {
|
||||
$uploaded ++;
|
||||
$file = '/' . urldecode( strstr( substr( $result['@metadata']["effectiveUri"], ( strrpos( $result['@metadata']["effectiveUri"], Infinite_Uploads::get_instance()->bucket ) + strlen( Infinite_Uploads::get_instance()->bucket ) ) ), '?', true ) ?: substr( $result['@metadata']["effectiveUri"], ( strrpos( $result['@metadata']["effectiveUri"], Infinite_Uploads::get_instance()->bucket ) + strlen( Infinite_Uploads::get_instance()->bucket ) ) ) );
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'synced' => 1, 'errors' => 0 ], [ 'file' => $file ] );
|
||||
|
||||
if ( $args_assoc['verbose'] ) {
|
||||
WP_CLI::success( sprintf( esc_html__( '%s - Synced %s of %s files.', 'infinite-uploads' ), $file, number_format_i18n( $uploaded ), number_format_i18n( $unsynced ) ) );
|
||||
} else {
|
||||
$progress_bar->tick();
|
||||
}
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
try {
|
||||
$manager = new Transfer( $s3, $from, 's3://' . $instance->bucket . '/', $transfer_args );
|
||||
$manager->transfer();
|
||||
} catch ( Exception $e ) {
|
||||
if ( method_exists( $e, 'getRequest' ) ) {
|
||||
$file = str_replace( trailingslashit( $instance->bucket ), '', $e->getRequest()->getRequestTarget() );
|
||||
$error_count = $wpdb->get_var( $wpdb->prepare( "SELECT errors FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE file = %s", $file ) );
|
||||
$error_count ++;
|
||||
if ( $error_count >= 3 ) {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'Error uploading %s. Retries exceeded.', 'infinite-uploads' ), $file ) );
|
||||
} else {
|
||||
WP_CLI::warning( sprintf( esc_html__( '%s error uploading %s. Queued for retry.', 'infinite-uploads' ), $e->getAwsErrorCode(), $file ) );
|
||||
}
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'errors' => $error_count ], [ 'file' => $file ] );
|
||||
|
||||
} elseif ( method_exists( $e, 'getMessage' ) ) {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'There was an error during upload: %s. Queued for retry.', 'infinite-uploads' ), $e->getMessage() ) );
|
||||
} else {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'There was an error during upload: %s. Queued for retry.', 'infinite-uploads' ), 'Unknown Error' ) );
|
||||
}
|
||||
}
|
||||
|
||||
$is_done = ! (bool) $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors < 3" );
|
||||
if ( $is_done ) {
|
||||
$break = true;
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['sync_finished'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
if ( ! $args_assoc['verbose'] ) {
|
||||
$progress_bar->finish();
|
||||
}
|
||||
|
||||
$error_count = $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors >= 3" );
|
||||
if ( $error_count ) {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'Unable to upload %s files.', 'infinite-uploads' ), number_format_i18n( $error_count ) ) );
|
||||
}
|
||||
WP_CLI::success( esc_html__( 'Sync complete!', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function build_scan() {
|
||||
global $wpdb;
|
||||
$instance = Infinite_Uploads::get_instance();
|
||||
$path = $instance->get_original_upload_dir_root();
|
||||
|
||||
WP_CLI::line( esc_html__( 'Scanning local filesystem...', 'infinite-uploads' ) );
|
||||
$filelist = new Infinite_Uploads_Filelist( $path['basedir'], 9999, [] );
|
||||
$filelist->start();
|
||||
|
||||
$stats = $instance->get_sync_stats();
|
||||
WP_CLI::line( sprintf( esc_html__( '%s files (%s) found in uploads.', 'infinite-uploads' ), $stats['local_files'], $stats['local_size'] ) );
|
||||
|
||||
//now verify that we are logged into cloud
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$s3 = $instance->s3();
|
||||
|
||||
WP_CLI::line( esc_html__( 'Comparing to the cloud...', 'infinite-uploads' ) );
|
||||
$prefix = '';
|
||||
|
||||
if ( strpos( $instance->bucket, '/' ) ) {
|
||||
$prefix = trailingslashit( str_replace( strtok( $instance->bucket, '/' ) . '/', '', $instance->bucket ) );
|
||||
}
|
||||
|
||||
$args = [
|
||||
'Bucket' => strtok( $instance->bucket, '/' ),
|
||||
'Prefix' => $prefix,
|
||||
];
|
||||
|
||||
//set flag
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['compare_started'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
|
||||
try {
|
||||
$results = $s3->getPaginator( 'ListObjectsV2', $args );
|
||||
foreach ( $results as $result ) {
|
||||
$cloud_only_files = [];
|
||||
if ( $result['Contents'] ) {
|
||||
foreach ( $result['Contents'] as $object ) {
|
||||
$local_key = str_replace( untrailingslashit( $prefix ), '', $object['Key'] );
|
||||
$file = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->base_prefix}infinite_uploads_files WHERE file = %s", $local_key ) );
|
||||
if ( $file && ! $file->synced && $file->size == $object['Size'] ) {
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'synced' => 1 ], [ 'file' => $local_key ] );
|
||||
}
|
||||
if ( ! $file ) {
|
||||
$cloud_only_files[] = [
|
||||
'name' => $local_key,
|
||||
'size' => $object['Size'],
|
||||
'mtime' => strtotime( $object['LastModified']->__toString() ),
|
||||
'type' => $instance->get_file_type( $local_key ),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
//flush new files to db
|
||||
if ( count( $cloud_only_files ) ) {
|
||||
$values = [];
|
||||
foreach ( $cloud_only_files as $file ) {
|
||||
$values[] = $wpdb->prepare( "(%s,%d,%d,%s,1,1)", $file['name'], $file['size'], $file['mtime'], $file['type'] );
|
||||
}
|
||||
|
||||
$query = "INSERT INTO {$wpdb->base_prefix}infinite_uploads_files (file, size, modified, type, synced, deleted) VALUES ";
|
||||
$query .= implode( ",\n", $values );
|
||||
$query .= " ON DUPLICATE KEY UPDATE size = VALUES(size), modified = VALUES(modified), type = VALUES(type), synced = 1, deleted = 1, errors = 0";
|
||||
$wpdb->query( $query );
|
||||
}
|
||||
}
|
||||
|
||||
//set flag
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['compare_finished'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all files from the uploads directory that have been synced to Infinite Uploads cloud storage.
|
||||
*
|
||||
* @subcommand delete
|
||||
* @synopsis [--noscan] [--verbose]
|
||||
*/
|
||||
public function delete( $args, $args_assoc ) {
|
||||
global $wpdb;
|
||||
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = Infinite_Uploads::get_instance();
|
||||
$args_assoc = wp_parse_args( $args_assoc, [ 'noscan' => false, 'verbose' => false ] );
|
||||
|
||||
$path = $instance->get_original_upload_dir_root();
|
||||
|
||||
if ( ! $args_assoc['noscan'] ) {
|
||||
$this->build_scan();
|
||||
$stats = $instance->get_sync_stats();
|
||||
WP_CLI::line( sprintf( esc_html__( '%s files (%s) remaining to be synced.', 'infinite-uploads' ), $stats['remaining_files'], $stats['remaining_size'] ) );
|
||||
}
|
||||
|
||||
//begin deleting
|
||||
$deleted = 0;
|
||||
$to_delete = $wpdb->get_col( "SELECT file FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 0" );
|
||||
$progress_bar = null;
|
||||
if ( ! $args_assoc['verbose'] ) {
|
||||
$progress_bar = \WP_CLI\Utils\make_progress_bar( esc_html__( 'Deleting local copies of synced files...', 'infinite-uploads' ), count( $to_delete ) );
|
||||
}
|
||||
|
||||
foreach ( $to_delete as $file ) {
|
||||
if ( @unlink( $path['basedir'] . $file ) ) {
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'deleted' => 1 ], [ 'file' => $file ] );
|
||||
$deleted ++;
|
||||
if ( $args_assoc['verbose'] ) {
|
||||
WP_CLI::success( sprintf( esc_html__( '%s - Deleted %s of %s files.', 'infinite-uploads' ), $file, number_format_i18n( $deleted ), number_format_i18n( count( $to_delete ) ) ) );
|
||||
} else {
|
||||
$progress_bar->tick();
|
||||
}
|
||||
} else {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'Could not delete %s.', 'infinite-uploads' ), $file ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $args_assoc['verbose'] ) {
|
||||
$progress_bar->finish();
|
||||
}
|
||||
WP_CLI::success( esc_html__( 'Delete complete!', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Download all files only in Infinite Uploads cloud storage to the local uploads directory.
|
||||
*
|
||||
* @subcommand download
|
||||
* @synopsis [--concurrency=<concurrency>] [--noscan] [--verbose]
|
||||
*/
|
||||
public function download( $args, $args_assoc ) {
|
||||
global $wpdb;
|
||||
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = Infinite_Uploads::get_instance();
|
||||
$s3 = $instance->s3();
|
||||
$args_assoc = wp_parse_args( $args_assoc, [ 'concurrency' => 20, 'noscan' => false, 'verbose' => false ] );
|
||||
|
||||
$path = $instance->get_original_upload_dir_root();
|
||||
|
||||
if ( ! $args_assoc['noscan'] ) {
|
||||
$this->build_scan();
|
||||
$unsynced = $wpdb->get_row( "SELECT count(*) AS files, SUM(`size`) as size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 1" );
|
||||
WP_CLI::line( sprintf( esc_html__( '%s files (%s) remaining to be downloaded.', 'infinite-uploads' ), $unsynced->files, size_format( $unsynced->size, 2 ) ) );
|
||||
}
|
||||
|
||||
//begin transfer
|
||||
if ( empty( $unsynced ) ) {
|
||||
$unsynced = $wpdb->get_row( "SELECT count(*) AS files, SUM(`size`) as size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 1" );
|
||||
}
|
||||
$progress_bar = null;
|
||||
if ( ! $args_assoc['verbose'] ) {
|
||||
$progress_bar = \WP_CLI\Utils\make_progress_bar( esc_html__( 'Downloading from the cloud...', 'infinite-uploads' ), $unsynced->files );
|
||||
}
|
||||
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
if ( empty( $progress['download_started'] ) ) {
|
||||
$progress['download_started'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
}
|
||||
|
||||
$downloaded = 0;
|
||||
$break = false;
|
||||
while ( ! $break ) {
|
||||
$to_sync = $wpdb->get_col( "SELECT file FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 1 AND errors < 3 LIMIT 1000" );
|
||||
//build full paths
|
||||
$to_sync_full = [];
|
||||
foreach ( $to_sync as $key => $file ) {
|
||||
$to_sync_full[] = 's3://' . untrailingslashit( $instance->bucket ) . $file;
|
||||
}
|
||||
|
||||
$obj = new ArrayObject( $to_sync_full );
|
||||
$from = $obj->getIterator();
|
||||
|
||||
$transfer_args = [
|
||||
'concurrency' => $args_assoc['concurrency'],
|
||||
'base_dir' => 's3://' . $instance->bucket,
|
||||
'before' => function ( UglyRobot\Infinite_Uploads\Aws\Command $command ) use ( $args_assoc, $progress_bar, $wpdb, $unsynced, &$downloaded ) {
|
||||
//add middleware to intercept result of each file upload
|
||||
if ( in_array( $command->getName(), [ 'GetObject' ], true ) ) {
|
||||
$command->getHandlerList()->appendSign(
|
||||
Middleware::mapResult( function ( ResultInterface $result ) use ( $args_assoc, $progress_bar, $command, $wpdb, $unsynced, &$downloaded ) {
|
||||
$downloaded ++;
|
||||
$file = '/' . urldecode( strstr( substr( $result['@metadata']["effectiveUri"], ( strrpos( $result['@metadata']["effectiveUri"], Infinite_Uploads::get_instance()->bucket ) + strlen( Infinite_Uploads::get_instance()->bucket ) ) ), '?', true ) ?: substr( $result['@metadata']["effectiveUri"], ( strrpos( $result['@metadata']["effectiveUri"], Infinite_Uploads::get_instance()->bucket ) + strlen( Infinite_Uploads::get_instance()->bucket ) ) ) );
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'deleted' => 0, 'errors' => 0 ], [ 'file' => $file ] );
|
||||
|
||||
if ( $args_assoc['verbose'] ) {
|
||||
WP_CLI::success( sprintf( esc_html__( '%s - Downloaded %s of %s files.', 'infinite-uploads' ), $file, number_format_i18n( $downloaded ), number_format_i18n( $unsynced->files ) ) );
|
||||
} else {
|
||||
$progress_bar->tick();
|
||||
}
|
||||
|
||||
return $result;
|
||||
} )
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
try {
|
||||
$manager = new Transfer( $s3, $from, $path['basedir'], $transfer_args );
|
||||
$manager->transfer();
|
||||
} catch ( Exception $e ) {
|
||||
if ( method_exists( $e, 'getRequest' ) ) {
|
||||
$file = str_replace( untrailingslashit( $path['basedir'] ), '', str_replace( trailingslashit( $instance->bucket ), '', $e->getRequest()->getRequestTarget() ) );
|
||||
$error_count = $wpdb->get_var( $wpdb->prepare( "SELECT errors FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE file = %s", $file ) );
|
||||
$error_count ++;
|
||||
if ( $error_count >= 3 ) {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'Error downloading %s. Retries exceeded.', 'infinite-uploads' ), $file ) );
|
||||
} else {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'Error downloading %s. Queued for retry.', 'infinite-uploads' ), $file ) );
|
||||
}
|
||||
$wpdb->update( "{$wpdb->base_prefix}infinite_uploads_files", [ 'errors' => $error_count ], [ 'file' => $file ] );
|
||||
} else {
|
||||
WP_CLI::warning( sprintf( esc_html__( '%s error downloading %s. Queued for retry.', 'infinite-uploads' ), $e->getAwsErrorCode(), $file ) );
|
||||
}
|
||||
}
|
||||
|
||||
$is_done = ! (bool) $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 1 AND errors < 3" );
|
||||
if ( $is_done ) {
|
||||
$break = true;
|
||||
$progress = get_site_option( 'iup_files_scanned' );
|
||||
$progress['download_finished'] = time();
|
||||
update_site_option( 'iup_files_scanned', $progress );
|
||||
if ( ! $args_assoc['verbose'] ) {
|
||||
$progress_bar->finish();
|
||||
}
|
||||
$error_count = $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 1 AND deleted = 1 AND errors >= 3" );
|
||||
if ( $error_count ) {
|
||||
WP_CLI::warning( sprintf( esc_html__( 'Unable to download %s files.', 'infinite-uploads' ), number_format_i18n( $error_count ) ) );
|
||||
}
|
||||
WP_CLI::success( esc_html__( 'Download complete!', 'infinite-uploads' ) );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete files from Infinite Uploads cloud
|
||||
*
|
||||
* @synopsis <path> [--regex=<regex>]
|
||||
*/
|
||||
public function rm( $args, $args_assoc ) {
|
||||
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$s3 = Infinite_Uploads::get_instance()->s3();
|
||||
|
||||
$prefix = '';
|
||||
$regex = isset( $args_assoc['regex'] ) ? $args_assoc['regex'] : '';
|
||||
|
||||
if ( strpos( Infinite_Uploads::get_instance()->bucket, '/' ) ) {
|
||||
$prefix = trailingslashit( str_replace( strtok( Infinite_Uploads::get_instance()->bucket, '/' ) . '/', '', Infinite_Uploads::get_instance()->bucket ) );
|
||||
}
|
||||
|
||||
if ( isset( $args[0] ) ) {
|
||||
$prefix .= ltrim( $args[0], '/' );
|
||||
|
||||
if ( strpos( $args[0], '.' ) === false ) {
|
||||
$prefix = trailingslashit( $prefix );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$s3->deleteMatchingObjects(
|
||||
strtok( Infinite_Uploads::get_instance()->bucket, '/' ),
|
||||
$prefix,
|
||||
$regex,
|
||||
[
|
||||
'before_delete',
|
||||
function () {
|
||||
WP_CLI::line( sprintf( 'Deleting file' ) );
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
|
||||
WP_CLI::success( sprintf( 'Successfully deleted %s', $prefix ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the auto-rewriting of media links to Infinite Uploads cloud
|
||||
*/
|
||||
public function enable( $args, $assoc_args ) {
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Infinite_Uploads::get_instance()->toggle_cloud( true );
|
||||
|
||||
WP_CLI::success( 'Media URL rewriting enabled.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the auto-rewriting of media links to Infinite Uploads cloud
|
||||
*/
|
||||
public function disable( $args, $assoc_args ) {
|
||||
Infinite_Uploads::get_instance()->toggle_cloud( false );
|
||||
|
||||
WP_CLI::success( 'Media URL rewriting disabled.' );
|
||||
}
|
||||
}
|
||||
|
||||
WP_CLI::add_command( 'infinite-uploads', 'Infinite_Uploads_WP_CLI_Command' );
|
@ -1,496 +0,0 @@
|
||||
<?php
|
||||
|
||||
if ( ! function_exists( 'wp_mail' ) ) :
|
||||
/**
|
||||
* Sends an email, similar to PHP's mail function.
|
||||
*
|
||||
* A true return value does not automatically mean that the user received the
|
||||
* email successfully. It just only means that the method used was able to
|
||||
* process the request without any errors.
|
||||
*
|
||||
* The default content type is `text/plain` which does not allow using HTML.
|
||||
* However, you can set the content type of the email by using the
|
||||
* {@see 'wp_mail_content_type'} filter.
|
||||
*
|
||||
* The default charset is based on the charset used on the blog. The charset can
|
||||
* be set using the {@see 'wp_mail_charset'} filter.
|
||||
*
|
||||
* @param string|string[] $to Array or comma-separated list of email addresses to send message.
|
||||
* @param string $subject Email subject.
|
||||
* @param string $message Message contents.
|
||||
* @param string|string[] $headers Optional. Additional headers.
|
||||
* @param string|string[] $attachments Optional. Paths to files to attach.
|
||||
*
|
||||
* @return bool Whether the email was sent successfully.
|
||||
* @since 1.2.1
|
||||
* @since 5.5.0 is_email() is used for email validation,
|
||||
* instead of PHPMailer's default validator.
|
||||
*
|
||||
* @global PHPMailer\PHPMailer\PHPMailer $phpmailer
|
||||
*
|
||||
*/
|
||||
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
|
||||
// Compact the input, apply the filters, and extract them back out.
|
||||
|
||||
/**
|
||||
* Filters the wp_mail() arguments.
|
||||
*
|
||||
* @param array $args {
|
||||
* Array of the `wp_mail()` arguments.
|
||||
*
|
||||
* @type string|string[] $to Array or comma-separated list of email addresses to send message.
|
||||
* @type string $subject Email subject.
|
||||
* @type string $message Message contents.
|
||||
* @type string|string[] $headers Additional headers.
|
||||
* @type string|string[] $attachments Paths to files to attach.
|
||||
* }
|
||||
* @since 2.2.0
|
||||
*
|
||||
*/
|
||||
$atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) );
|
||||
|
||||
/**
|
||||
* Filters whether to preempt sending an email.
|
||||
*
|
||||
* Returning a non-null value will short-circuit {@see wp_mail()}, returning
|
||||
* that value instead. A boolean return value should be used to indicate whether
|
||||
* the email was successfully sent.
|
||||
*
|
||||
* @param null|bool $return Short-circuit return value.
|
||||
* @param array $atts {
|
||||
* Array of the `wp_mail()` arguments.
|
||||
*
|
||||
* @type string|string[] $to Array or comma-separated list of email addresses to send message.
|
||||
* @type string $subject Email subject.
|
||||
* @type string $message Message contents.
|
||||
* @type string|string[] $headers Additional headers.
|
||||
* @type string|string[] $attachments Paths to files to attach.
|
||||
* }
|
||||
* @since 5.7.0
|
||||
*
|
||||
*/
|
||||
$pre_wp_mail = apply_filters( 'pre_wp_mail', null, $atts );
|
||||
|
||||
if ( null !== $pre_wp_mail ) {
|
||||
return $pre_wp_mail;
|
||||
}
|
||||
|
||||
if ( isset( $atts['to'] ) ) {
|
||||
$to = $atts['to'];
|
||||
}
|
||||
|
||||
if ( ! is_array( $to ) ) {
|
||||
$to = explode( ',', $to );
|
||||
}
|
||||
|
||||
if ( isset( $atts['subject'] ) ) {
|
||||
$subject = $atts['subject'];
|
||||
}
|
||||
|
||||
if ( isset( $atts['message'] ) ) {
|
||||
$message = $atts['message'];
|
||||
}
|
||||
|
||||
if ( isset( $atts['headers'] ) ) {
|
||||
$headers = $atts['headers'];
|
||||
}
|
||||
|
||||
if ( isset( $atts['attachments'] ) ) {
|
||||
$attachments = $atts['attachments'];
|
||||
}
|
||||
|
||||
if ( ! is_array( $attachments ) ) {
|
||||
$attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
|
||||
}
|
||||
global $phpmailer;
|
||||
|
||||
// (Re)create it, if it's gone missing.
|
||||
if ( ! ( $phpmailer instanceof Infinite_Uploads_PHPMailer ) ) {
|
||||
require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
|
||||
require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
|
||||
require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
|
||||
$phpmailer = new Infinite_Uploads_PHPMailer( true );
|
||||
|
||||
$phpmailer::$validator = static function ( $email ) {
|
||||
return (bool) is_email( $email );
|
||||
};
|
||||
}
|
||||
|
||||
// Headers.
|
||||
$cc = array();
|
||||
$bcc = array();
|
||||
$reply_to = array();
|
||||
|
||||
if ( empty( $headers ) ) {
|
||||
$headers = array();
|
||||
} else {
|
||||
if ( ! is_array( $headers ) ) {
|
||||
// Explode the headers out, so this function can take
|
||||
// both string headers and an array of headers.
|
||||
$tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
|
||||
} else {
|
||||
$tempheaders = $headers;
|
||||
}
|
||||
$headers = array();
|
||||
|
||||
// If it's actually got contents.
|
||||
if ( ! empty( $tempheaders ) ) {
|
||||
// Iterate through the raw headers.
|
||||
foreach ( (array) $tempheaders as $header ) {
|
||||
if ( strpos( $header, ':' ) === false ) {
|
||||
if ( false !== stripos( $header, 'boundary=' ) ) {
|
||||
$parts = preg_split( '/boundary=/i', trim( $header ) );
|
||||
$boundary = trim( str_replace( array( "'", '"' ), '', $parts[1] ) );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Explode them out.
|
||||
list( $name, $content ) = explode( ':', trim( $header ), 2 );
|
||||
|
||||
// Cleanup crew.
|
||||
$name = trim( $name );
|
||||
$content = trim( $content );
|
||||
|
||||
switch ( strtolower( $name ) ) {
|
||||
// Mainly for legacy -- process a "From:" header if it's there.
|
||||
case 'from':
|
||||
$bracket_pos = strpos( $content, '<' );
|
||||
if ( false !== $bracket_pos ) {
|
||||
// Text before the bracketed email is the "From" name.
|
||||
if ( $bracket_pos > 0 ) {
|
||||
$from_name = substr( $content, 0, $bracket_pos );
|
||||
$from_name = str_replace( '"', '', $from_name );
|
||||
$from_name = trim( $from_name );
|
||||
}
|
||||
|
||||
$from_email = substr( $content, $bracket_pos + 1 );
|
||||
$from_email = str_replace( '>', '', $from_email );
|
||||
$from_email = trim( $from_email );
|
||||
|
||||
// Avoid setting an empty $from_email.
|
||||
} elseif ( '' !== trim( $content ) ) {
|
||||
$from_email = trim( $content );
|
||||
}
|
||||
break;
|
||||
case 'content-type':
|
||||
if ( strpos( $content, ';' ) !== false ) {
|
||||
list( $type, $charset_content ) = explode( ';', $content );
|
||||
$content_type = trim( $type );
|
||||
if ( false !== stripos( $charset_content, 'charset=' ) ) {
|
||||
$charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
|
||||
} elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
|
||||
$boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
|
||||
$charset = '';
|
||||
}
|
||||
|
||||
// Avoid setting an empty $content_type.
|
||||
} elseif ( '' !== trim( $content ) ) {
|
||||
$content_type = trim( $content );
|
||||
}
|
||||
break;
|
||||
case 'cc':
|
||||
$cc = array_merge( (array) $cc, explode( ',', $content ) );
|
||||
break;
|
||||
case 'bcc':
|
||||
$bcc = array_merge( (array) $bcc, explode( ',', $content ) );
|
||||
break;
|
||||
case 'reply-to':
|
||||
$reply_to = array_merge( (array) $reply_to, explode( ',', $content ) );
|
||||
break;
|
||||
default:
|
||||
// Add it to our grand headers array.
|
||||
$headers[ trim( $name ) ] = trim( $content );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty out the values that may be set.
|
||||
$phpmailer->clearAllRecipients();
|
||||
$phpmailer->clearAttachments();
|
||||
$phpmailer->clearCustomHeaders();
|
||||
$phpmailer->clearReplyTos();
|
||||
$phpmailer->Body = '';
|
||||
$phpmailer->AltBody = '';
|
||||
|
||||
// Set "From" name and email.
|
||||
|
||||
// If we don't have a name from the input headers.
|
||||
if ( ! isset( $from_name ) ) {
|
||||
$from_name = 'WordPress';
|
||||
}
|
||||
|
||||
/*
|
||||
* If we don't have an email from the input headers, default to wordpress@$sitename
|
||||
* Some hosts will block outgoing mail from this address if it doesn't exist,
|
||||
* but there's no easy alternative. Defaulting to admin_email might appear to be
|
||||
* another option, but some hosts may refuse to relay mail from an unknown domain.
|
||||
* See https://core.trac.wordpress.org/ticket/5007.
|
||||
*/
|
||||
if ( ! isset( $from_email ) ) {
|
||||
// Get the site domain and get rid of www.
|
||||
$sitename = wp_parse_url( network_home_url(), PHP_URL_HOST );
|
||||
$from_email = 'wordpress@';
|
||||
|
||||
if ( null !== $sitename ) {
|
||||
if ( 'www.' === substr( $sitename, 0, 4 ) ) {
|
||||
$sitename = substr( $sitename, 4 );
|
||||
}
|
||||
|
||||
$from_email .= $sitename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the email address to send from.
|
||||
*
|
||||
* @param string $from_email Email address to send from.
|
||||
*
|
||||
* @since 2.2.0
|
||||
*
|
||||
*/
|
||||
$from_email = apply_filters( 'wp_mail_from', $from_email );
|
||||
|
||||
/**
|
||||
* Filters the name to associate with the "from" email address.
|
||||
*
|
||||
* @param string $from_name Name associated with the "from" email address.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
*/
|
||||
$from_name = apply_filters( 'wp_mail_from_name', $from_name );
|
||||
|
||||
try {
|
||||
$phpmailer->setFrom( $from_email, $from_name, false );
|
||||
} catch ( PHPMailer\PHPMailer\Exception $e ) {
|
||||
$mail_error_data = compact( 'to', 'subject', 'message', 'headers', 'attachments' );
|
||||
$mail_error_data['phpmailer_exception_code'] = $e->getCode();
|
||||
|
||||
/** This filter is documented in wp-includes/pluggable.php */
|
||||
do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $e->getMessage(), $mail_error_data ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set mail's subject and body.
|
||||
$phpmailer->Subject = $subject;
|
||||
$phpmailer->Body = $message;
|
||||
|
||||
// Set destination addresses, using appropriate methods for handling addresses.
|
||||
$address_headers = compact( 'to', 'cc', 'bcc', 'reply_to' );
|
||||
|
||||
foreach ( $address_headers as $address_header => $addresses ) {
|
||||
if ( empty( $addresses ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( (array) $addresses as $address ) {
|
||||
try {
|
||||
// Break $recipient into name and address parts if in the format "Foo <bar@baz.com>".
|
||||
$recipient_name = '';
|
||||
|
||||
if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) ) {
|
||||
if ( count( $matches ) == 3 ) {
|
||||
$recipient_name = $matches[1];
|
||||
$address = $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
switch ( $address_header ) {
|
||||
case 'to':
|
||||
$phpmailer->addAddress( $address, $recipient_name );
|
||||
break;
|
||||
case 'cc':
|
||||
$phpmailer->addCc( $address, $recipient_name );
|
||||
break;
|
||||
case 'bcc':
|
||||
$phpmailer->addBcc( $address, $recipient_name );
|
||||
break;
|
||||
case 'reply_to':
|
||||
$phpmailer->addReplyTo( $address, $recipient_name );
|
||||
break;
|
||||
}
|
||||
} catch ( PHPMailer\PHPMailer\Exception $e ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set to use PHP's mail().
|
||||
$phpmailer->isMail();
|
||||
|
||||
// Set Content-Type and charset.
|
||||
|
||||
// If we don't have a content-type from the input headers.
|
||||
if ( ! isset( $content_type ) ) {
|
||||
$content_type = 'text/plain';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the wp_mail() content type.
|
||||
*
|
||||
* @param string $content_type Default wp_mail() content type.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
*/
|
||||
$content_type = apply_filters( 'wp_mail_content_type', $content_type );
|
||||
|
||||
$phpmailer->ContentType = $content_type;
|
||||
|
||||
// Set whether it's plaintext, depending on $content_type.
|
||||
if ( 'text/html' === $content_type ) {
|
||||
$phpmailer->isHTML( true );
|
||||
}
|
||||
|
||||
// If we don't have a charset from the input headers.
|
||||
if ( ! isset( $charset ) ) {
|
||||
$charset = get_bloginfo( 'charset' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the default wp_mail() charset.
|
||||
*
|
||||
* @param string $charset Default email charset.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
*/
|
||||
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );
|
||||
|
||||
// Set custom headers.
|
||||
if ( ! empty( $headers ) ) {
|
||||
foreach ( (array) $headers as $name => $content ) {
|
||||
// Only add custom headers not added automatically by PHPMailer.
|
||||
if ( ! in_array( $name, array( 'MIME-Version', 'X-Mailer' ), true ) ) {
|
||||
try {
|
||||
$phpmailer->addCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
|
||||
} catch ( PHPMailer\PHPMailer\Exception $e ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) {
|
||||
$phpmailer->addCustomHeader( sprintf( 'Content-Type: %s; boundary="%s"', $content_type, $boundary ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $attachments ) ) {
|
||||
foreach ( $attachments as $attachment ) {
|
||||
try {
|
||||
$phpmailer->addAttachment( $attachment );
|
||||
} catch ( PHPMailer\PHPMailer\Exception $e ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after PHPMailer is initialized.
|
||||
*
|
||||
* @param PHPMailer $phpmailer The PHPMailer instance (passed by reference).
|
||||
*
|
||||
* @since 2.2.0
|
||||
*
|
||||
*/
|
||||
do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );
|
||||
|
||||
$mail_data = compact( 'to', 'subject', 'message', 'headers', 'attachments' );
|
||||
|
||||
// Send!
|
||||
try {
|
||||
$send = $phpmailer->send();
|
||||
|
||||
/**
|
||||
* Fires after PHPMailer has successfully sent an email.
|
||||
*
|
||||
* The firing of this action does not necessarily mean that the recipient(s) received the
|
||||
* email successfully. It only means that the `send` method above was able to
|
||||
* process the request without any errors.
|
||||
*
|
||||
* @param array $mail_data {
|
||||
* An array containing the email recipient(s), subject, message, headers, and attachments.
|
||||
*
|
||||
* @type string[] $to Email addresses to send message.
|
||||
* @type string $subject Email subject.
|
||||
* @type string $message Message contents.
|
||||
* @type string[] $headers Additional headers.
|
||||
* @type string[] $attachments Paths to files to attach.
|
||||
* }
|
||||
* @since 5.9.0
|
||||
*
|
||||
*/
|
||||
do_action( 'wp_mail_succeeded', $mail_data );
|
||||
|
||||
return $send;
|
||||
} catch ( PHPMailer\PHPMailer\Exception $e ) {
|
||||
$mail_data['phpmailer_exception_code'] = $e->getCode();
|
||||
|
||||
/**
|
||||
* Fires after a PHPMailer\PHPMailer\Exception is caught.
|
||||
*
|
||||
* @param WP_Error $error A WP_Error object with the PHPMailer\PHPMailer\Exception message, and an array
|
||||
* containing the mail recipient, subject, message, headers, and attachments.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
*/
|
||||
do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $e->getMessage(), $mail_data ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
endif;
|
||||
|
||||
require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
|
||||
|
||||
/**
|
||||
* PHPMailer - PHP email creation and transport class.
|
||||
*
|
||||
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
|
||||
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
|
||||
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
|
||||
* @author Brent R. Matzelle (original founder)
|
||||
*/
|
||||
class Infinite_Uploads_PHPMailer extends PHPMailer\PHPMailer\PHPMailer {
|
||||
/**
|
||||
* Check whether a file path is of a permitted type.
|
||||
* Used to reject URLs and phar files from functions that access local file paths,
|
||||
* such as addAttachment.
|
||||
*
|
||||
* @param string $path A relative or absolute path to a file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isPermittedPath( $path ) {
|
||||
|
||||
return true;
|
||||
|
||||
//Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1
|
||||
return ! preg_match( '#^[a-z][a-z\d+.-]*://#i', $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file path is safe, accessible, and readable.
|
||||
*
|
||||
* @param string $path A relative or absolute path to a file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function fileIsAccessible( $path ) {
|
||||
|
||||
if ( ! static::isPermittedPath( $path ) ) {
|
||||
return false;
|
||||
}
|
||||
$readable = is_file( $path );
|
||||
//If not a UNC path (expected to start with \\), check read permission, see #2069
|
||||
if ( strpos( $path, '\\\\' ) !== 0 ) {
|
||||
$readable = $readable && is_readable( $path );
|
||||
}
|
||||
|
||||
return $readable;
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="m-0 mr-auto p-0"><?php esc_html_e( 'Cloud Storage & CDN Overview', 'infinite-uploads' ); ?></h5>
|
||||
<?php require_once( dirname( __FILE__ ) . '/status-icon.php' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body cloud p-md-3">
|
||||
<div class="row align-items-center justify-content-center mb-5">
|
||||
<div class="col-lg col-xs-12 mx-sm-0">
|
||||
<p class="lead mb-0"><?php esc_html_e( "This Site's Cloud Bytes / Files", 'infinite-uploads' ); ?> <span class="dashicons dashicons-info text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Recalculated every 24 hours', 'infinite-uploads' ); ?>"></span></p>
|
||||
<span class="h2 text-nowrap"><?php echo $this->size_format_zero( $cloud_size, 2 ); ?><small class="text-muted"> / <?php echo number_format_i18n( $cloud_files ); ?></small></span>
|
||||
|
||||
<div class="container p-0 ml-md-3">
|
||||
<?php foreach ( $this->iup_instance->get_filetypes( false, $api_data->stats->site->types ) as $type ) { ?>
|
||||
<div class="row mt-2">
|
||||
<div class="col-1"><span class="badge badge-pill" style="background-color: <?php echo $type->color; ?>"> </span></div>
|
||||
<div class="col-4 lead text-nowrap"><?php echo $type->label; ?></div>
|
||||
<div class="col-5 text-nowrap"><strong><?php echo size_format( $type->size, 2 ); ?> / <?php echo number_format_i18n( $type->files ); ?></strong></div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg col-xs-12 text-center mt-5 mt-lg-0 iup-pie-wrapper">
|
||||
<p class="h5"><?php printf( esc_html__( '%s / %s', 'infinite-uploads' ), $this->size_format_zero( $cloud_total_size, 2 ), esc_html( $api_data->plan->label ) ); ?></p>
|
||||
<canvas id="iup-cloud-pie"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-1">
|
||||
<div class="col text-center">
|
||||
<p><?php esc_html_e( 'Visit the Infinite Uploads site to view, manage, or change your plan.', 'infinite-uploads' ); ?></p>
|
||||
<a class="btn text-nowrap btn-info btn-lg" href="<?php echo esc_url( $this->api_url( '/account/' ) ); ?>" role="button"><?php esc_html_e( 'Account Management', 'infinite-uploads' ); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,60 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="m-0 mr-auto p-0"><?php esc_html_e( 'Local File Overview', 'infinite-uploads' ); ?></h5>
|
||||
<?php require_once( dirname( __FILE__ ) . '/status-icon.php' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body cloud p-md-5">
|
||||
<div class="row align-items-center justify-content-center mb-5">
|
||||
<div class="col-lg col-xs-12">
|
||||
<p class="lead mb-0"><?php esc_html_e( "Total Bytes / Files", 'infinite-uploads' ); ?></p>
|
||||
<span class="h2 text-nowrap"><?php echo $stats['local_size']; ?><small class="text-muted"> / <?php echo $stats['local_files']; ?></small></span>
|
||||
|
||||
<div class="container p-0 ml-md-3">
|
||||
<?php foreach ( $this->iup_instance->get_filetypes( false ) as $type ) { ?>
|
||||
<div class="row mt-2">
|
||||
<div class="col-1"><span class="badge badge-pill" style="background-color: <?php echo $type->color; ?>"> </span></div>
|
||||
<div class="col-4 lead text-nowrap"><?php echo $type->label; ?></div>
|
||||
<div class="col-5 text-nowrap"><strong><?php echo size_format( $type->size, 2 ); ?> / <?php echo number_format_i18n( $type->files ); ?></strong></div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="row mt-2">
|
||||
<div class="col text-muted"><small><?php printf( esc_html__( 'Scanned %s ago', 'infinite-uploads' ), human_time_diff( $stats['files_finished'] ) ); ?> ‐ <a href="#" class="badge badge-primary" data-toggle="modal" data-target="#scan-modal"><span
|
||||
data-toggle="tooltip"
|
||||
title="<?php esc_attr_e( 'Run a new scan to detect and sync recently uploaded files.', 'infinite-uploads' ); ?>"><?php esc_html_e( 'Refresh', 'infinite-uploads' ); ?></span></a></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg col-xs-12 mt-5 mt-lg-0 text-center iup-pie-wrapper">
|
||||
<canvas id="iup-local-pie"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-3">
|
||||
<div class="col text-center">
|
||||
<h4><?php esc_html_e( 'Ready to Connect!', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( 'Get smart plan recommendations, create or connect to existing account, and enable video or sync to the cloud.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col text-center">
|
||||
<form method="post"
|
||||
action="<?php echo esc_url( $this->api_url( defined( 'BIG_FILE_UPLOADS_VERSION' ) ? '/connect/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=bfu_plugin&utm_term=connect' : '/connect/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin&utm_term=connect' ) ); ?>">
|
||||
<input type="hidden" name="action" value="iup_connect">
|
||||
<input type="hidden" name="site_id" value="<?php echo esc_attr( $this->api->get_site_id() ); ?>">
|
||||
<input type="hidden" name="domain" value="<?php echo esc_url( $this->api->network_site_url() ); ?>">
|
||||
<input type="hidden" name="redirect_url" value="<?php echo esc_url( $this->settings_url() ); ?>">
|
||||
<input type="hidden" name="bytes" value="<?php echo esc_attr( $to_sync->size ); ?>">
|
||||
<input type="hidden" name="files" value="<?php echo esc_attr( $to_sync->files ); ?>">
|
||||
<button class="btn text-nowrap btn-primary btn-lg" type="submit"><span class="dashicons dashicons-cloud"></span> <?php esc_html_e( 'Connect', 'infinite-uploads' ); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-1">
|
||||
<div class="col text-center">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/progress-bar-2.svg', dirname( __FILE__ ) ) ); ?>" alt="Progress steps bar" height="19" width="110"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,23 +0,0 @@
|
||||
<div id="iup-footer" class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-sm text-center text-muted">
|
||||
<strong><?php esc_html_e( "The Cloud by Infinite Uploads", 'infinite-uploads' ); ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm text-center text-muted">
|
||||
<a href="<?php echo esc_url( Infinite_Uploads_Admin::get_instance()->api_url( '/support/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin&utm_content=footer&utm_term=support' ) ); ?>"
|
||||
class="text-muted text-decoration-none"><?php esc_html_e( "Support", 'infinite-uploads' ); ?></a> |
|
||||
<a href="<?php echo esc_url( Infinite_Uploads_Admin::get_instance()->api_url( '/terms-of-service/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin&utm_content=footer&utm_term=terms' ) ); ?>"
|
||||
class="text-muted text-decoration-none"><?php esc_html_e( "Terms of Service", 'infinite-uploads' ); ?></a> |
|
||||
<a href="<?php echo esc_url( Infinite_Uploads_Admin::get_instance()->api_url( '/privacy/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin&utm_content=footer&utm_term=privacy' ) ); ?>"
|
||||
class="text-muted text-decoration-none"><?php esc_html_e( "Privacy Policy", 'infinite-uploads' ); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm text-center text-muted">
|
||||
<a href="https://twitter.com/infiniteuploads" class="text-muted text-decoration-none" data-toggle="tooltip" title="<?php esc_attr_e( 'Twitter', 'infinite-uploads' ); ?>"><span class="dashicons dashicons-twitter"></span></a>
|
||||
<a href="https://www.facebook.com/infiniteuploads/" class="text-muted text-decoration-none" data-toggle="tooltip" title="<?php esc_attr_e( 'Facebook', 'infinite-uploads' ); ?>"><span class="dashicons dashicons-facebook-alt"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,21 +0,0 @@
|
||||
<div class="row mt-2">
|
||||
<div class="col-12 col-lg-6 mb-4">
|
||||
<?php
|
||||
if ( infinite_uploads_enabled() ) {
|
||||
require_once( dirname( __FILE__ ) . '/cloud-overview.php' );
|
||||
} else {
|
||||
require_once( dirname( __FILE__ ) . '/sync.php' );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 mb-4">
|
||||
<?php
|
||||
if ( $this->video->is_video_active() ) {
|
||||
$video_library_settings = $this->video->get_library_settings();
|
||||
require_once( dirname( __FILE__ ) . '/video-overview.php' );
|
||||
} else {
|
||||
require_once( dirname( __FILE__ ) . '/video-disabled.php' );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
@ -1,15 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body cloud p-md-5">
|
||||
<div class="row justify-content-center mb-5 mt-3">
|
||||
<div class="col text-center">
|
||||
<h4 class="text-warning"><?php esc_html_e( 'Installation Error', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( "We are so sorry, there appears to have been a problem installing the needed tables for the Infinite Uploads plugin. We want to get this working for you so please contact us and we will help you out!", 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col text-center">
|
||||
<a class="btn text-nowrap btn-info btn-lg" href="<?php echo esc_url( $this->api_url( '/support/?utm_source=iup_plugin&utm_medium=plugin&utm_campaign=iup_plugin&utm_content=error&utm_term=support' ) ); ?>" role="button"><?php esc_html_e( 'Contact Support', 'infinite-uploads' ); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,36 +0,0 @@
|
||||
<div class="modal fade" id="delete-modal" tabindex="-1" role="dialog" aria-labelledby="delete-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="delete-modal-label"><?php esc_html_e( 'Free Up Local Storage', 'infinite-uploads' ); ?></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center mb-4 mt-3">
|
||||
<div class="col text-center">
|
||||
<h4><?php esc_html_e( 'Delete Local Files', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( "This will delete the duplicate copies of your files stored in your local media library. This saves space and improves server performance but will require downloading these files back to the uploads directory before disconnecting to prevent broken media on your site.", 'infinite-uploads' ); ?></p>
|
||||
<p><?php esc_html_e( 'If your host provides access to WP CLI, you can also execute the command:', 'infinite-uploads' ); ?> <code>wp infinite-uploads delete</code></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col text-center text-muted">
|
||||
<div id="iup-delete-local-spinner" class="spinner-border spinner-border-sm" role="status" style="display: none;">
|
||||
<span class="sr-only">Deleting...</span>
|
||||
</div>
|
||||
<span class="h5"><?php printf( __( '<span id="iup-delete-size">%s</span> / <span id="iup-delete-files">%s</span> Deletable Files', 'infinite-uploads' ), $stats['deletable_size'], $stats['deletable_files'] ); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<button class="btn text-nowrap btn-info btn-lg" id="iup-delete-local-button"><?php esc_html_e( 'Start Delete', 'infinite-uploads' ); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,42 +0,0 @@
|
||||
<div class="modal fade" id="download-modal" tabindex="-1" role="dialog" aria-labelledby="download-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="download-modal-label"><?php esc_html_e( 'Download & Disconnect', 'infinite-uploads' ); ?></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center mb-4 mt-3">
|
||||
<div class="col text-center">
|
||||
<img class="mb-4" src="<?php echo esc_url( plugins_url( '/assets/img/download-from-cloud.svg', dirname( __FILE__ ) ) ); ?>" alt="Download from Cloud" height="76" width="76"/>
|
||||
<h4><?php esc_html_e( 'Downloading Files', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( "This process can take many hours for very large media libraries with a lot of files. Please leave this tab open while the download is being processed. If you close the tab the download will be interrupted and you will have to continue where you left off later.", 'infinite-uploads' ); ?></p>
|
||||
<p><?php esc_html_e( 'If your host provides access to WP CLI, that is the fastest and most efficient way to sync your files. Simply execute the command:', 'infinite-uploads' ); ?> <code>wp infinite-uploads download</code></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<div id="iup-download-errors" class="alert alert-warning text-left" role="alert">
|
||||
<ul class="mb-0 mb-lc-0"></ul>
|
||||
</div>
|
||||
<div class="progress download">
|
||||
<div id="iup-download-progress-bar" class="progress-bar progress-bar-animated progress-bar-striped" role="progressbar" style="width: <?php echo $stats['pcnt_downloaded']; ?>%;" aria-valuenow="<?php echo $stats['pcnt_downloaded']; ?>" aria-valuemin="0"
|
||||
aria-valuemax="100"><?php echo $stats['pcnt_downloaded']; ?>%
|
||||
</div>
|
||||
</div>
|
||||
<div class="col text-center text-muted">
|
||||
<div class=" spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">Downloading...</span>
|
||||
</div>
|
||||
<span class="h6" id="iup-download-progress"><?php printf( __( '<span id="iup-download-size">%s</span> / <span id="iup-download-files">%s</span> File(s) Remaining', 'infinite-uploads' ), $stats['deleted_size'], $stats['deleted_files'] ); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,64 +0,0 @@
|
||||
<div class="modal fade" id="enable-modal" tabindex="-1" role="dialog" aria-labelledby="enable-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="enable-modal-label"><?php esc_html_e( 'Enable Infinite Uploads', 'infinite-uploads' ); ?></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center mb-4 mt-3">
|
||||
<div class="col text-center">
|
||||
<h4><?php esc_html_e( 'Enable the Infinite Uploads Cloud', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( 'Your media library has finished syncing to the Infinite Uploads cloud. Enable now to serve all media from the cloud and global CDN. All new media uploaded will skip the local filesystem and be synced directly to the Infinite Uploads cloud.', 'infinite-uploads' ); ?></p>
|
||||
<?php $error_count = $wpdb->get_var( "SELECT count(*) FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors >= 3" ); ?>
|
||||
<div id="iup-enable-errors" class="alert alert-warning text-left iup-enable-errors" role="alert" <?php echo ( $error_count ) ? '' : 'style="display:none;"'; ?>>
|
||||
<?php printf( __( '<span>%s</span> file(s) errored while syncing to the cloud.', 'infinite-uploads' ), number_format_i18n( $error_count ) ); ?>
|
||||
<a class="alert-link" data-toggle="collapse" href="#iup-collapse-errors" role="button" aria-expanded="false" aria-controls="iup-collapse-errors" title="<?php esc_attr_e( 'Show errors', 'infinite-uploads' ); ?>">
|
||||
<span class="dashicons dashicons-arrow-down-alt2"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="collapse" id="iup-collapse-errors">
|
||||
<div class="card card-body">
|
||||
<ul class="list-group list-group-flush text-left">
|
||||
<?php
|
||||
$error_list = $wpdb->get_results( "SELECT file, size FROM `{$wpdb->base_prefix}infinite_uploads_files` WHERE synced = 0 AND errors >= 3" );
|
||||
if ( count( $error_list ) ) {
|
||||
foreach ( $error_list as $error ) { ?>
|
||||
<li class="list-group-item list-group-item-warning"><?php echo esc_html( $error->file ); ?> - <?php echo size_format( $error->size, 2 ); ?></li>
|
||||
<?php
|
||||
}
|
||||
} else { ?>
|
||||
<li class="list-group-item list-group-item-warning">
|
||||
<div class="spinner-grow spinner-grow-sm" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-warning iup-enable-errors" <?php echo ( $error_count ) ? '' : 'style="display:none;"'; ?>><?php esc_html_e( 'Note: If any of these files are referenced in posts or pages they might show as missing after enabling. You can retry syncing them, or ignore and enable anyway.', 'infinite-uploads' ); ?>
|
||||
<a href="<?php echo esc_url( $this->api_url( '/support/' ) ); ?>"><?php esc_html_e( 'Need help?', 'infinite-uploads' ); ?></a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<button class="btn text-nowrap btn-primary btn-lg mr-2 iup-enable-errors" id="iup-resync-button" data-toggle="modal" <?php echo ( $error_count ) ? '' : 'style="display:none;"'; ?>><span
|
||||
class="dashicons dashicons-cloud"></span> <?php esc_html_e( 'Retry Sync', 'infinite-uploads' ); ?></button>
|
||||
<button class="btn text-nowrap btn-info btn-lg" id="iup-enable-button"><span class="dashicons dashicons-cloud-saved"></span><?php esc_html_e( 'Enable', 'infinite-uploads' ); ?></button>
|
||||
<div class="spinner-grow text-muted text-hide d-block mx-auto" id="iup-enable-spinner" role="status"><span class="sr-only"><?php esc_html_e( 'Enabling...', 'iup' ); ?></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/progress-bar-5.svg', dirname( __FILE__ ) ) ); ?>" alt="Progress steps bar" height="19" width="110"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,38 +0,0 @@
|
||||
<div class="modal fade" id="scan-remote-modal" tabindex="-1" role="dialog" aria-labelledby="scan-remote-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="scan-modal-label"><?php esc_html_e( 'Scanning Cloud', 'infinite-uploads' ); ?></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center mb-4 mt-3">
|
||||
<div class="col text-center">
|
||||
<div class="mb-4 mx-auto" style="width: 76px; height: 76px;">
|
||||
<?php include( dirname( dirname( __FILE__ ) ) . '/assets/img/spinner-svg-2.html' ); ?>
|
||||
</div>
|
||||
<h4><?php esc_html_e( 'Comparing to Cloud', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( "Checking for files already existing in the cloud. Please leave this tab open while we complete your scan.", 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center text-muted">
|
||||
<span
|
||||
class="h5" <?php echo ! $stats['cloud_files'] ? 'id="iup-scan-remote-progress"' : ''; ?>><?php printf( __( '<span id="iup-scan-remote-storage">%s</span> / <span id="iup-scan-remote-files">%s</span> Files Synced', 'infinite-uploads' ), $stats['cloud_size'], $stats['cloud_files'] ); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ( ! infinite_uploads_enabled() ) { ?>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/progress-bar-3.svg', dirname( __FILE__ ) ) ); ?>" alt="Progress steps bar" height="19" width="110"/>
|
||||
</div>
|
||||
</div>
|
||||
<?php } //end not enabled ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,45 +0,0 @@
|
||||
<div class="modal fade" id="scan-modal" tabindex="-1" role="dialog" aria-labelledby="scan-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="scan-modal-label"><?php esc_html_e( 'Scanning Files', 'infinite-uploads' ); ?></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center mb-4 mt-3">
|
||||
<div class="col text-center">
|
||||
<div class="mb-4 mx-auto" style="width: 76px; height: 76px;">
|
||||
<?php include( dirname( dirname( __FILE__ ) ) . '/assets/img/spinner-svg.html' ); ?>
|
||||
</div>
|
||||
<h4><?php esc_html_e( 'Scanning Local Filesystem', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( "This usually only takes a minute or two but can take longer for very large media libraries with a lot of files. Please leave this tab open while we complete your scan.", 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center text-muted">
|
||||
<span class="h5" id="iup-scan-progress">
|
||||
<?php
|
||||
printf(
|
||||
// translators: %1$s is the opening a tag for storage
|
||||
// translators: %2$s is the closing a tag for storage
|
||||
// translators: %3$s is the opening a tag for files
|
||||
// translators: %4$s is the closing a tag for files
|
||||
esc_html__( 'Found %1$s0 MB%2$s / %3$s0%4$s Files...', 'infinite-uploads' ),
|
||||
'<span id="iup-scan-storage">', '</span>', '<span id="iup-scan-files">', '</span>' );
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/progress-bar-1.svg', dirname( __FILE__ ) ) ); ?>" alt="Progress steps bar" height="19" width="110"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,47 +0,0 @@
|
||||
<div class="modal fade" id="upload-modal" tabindex="-1" role="dialog" aria-labelledby="upload-modal-label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="upload-modal-label"><?php esc_html_e( 'Upload to Cloud', 'infinite-uploads' ); ?></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center mb-4 mt-3">
|
||||
<div class="col text-center">
|
||||
<img class="mb-4" src="<?php echo esc_url( plugins_url( '/assets/img/push-to-cloud.svg', dirname( __FILE__ ) ) ); ?>" alt="Push to Cloud" height="76" width="76"/>
|
||||
<h4><?php esc_html_e( 'Sync in Progress', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( "This process can take many hours for very large media libraries with a lot of files. Please leave this tab open while the sync is being processed. If you close the tab the sync will be interrupted and you will have to continue where you left off later.", 'infinite-uploads' ); ?></p>
|
||||
<p><?php esc_html_e( 'If your host provides access to WP CLI, that is the fastest and most efficient way to sync your files. Simply execute the command:', 'infinite-uploads' ); ?> <code>wp infinite-uploads sync</code></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<div id="iup-sync-errors" class="alert alert-warning text-left" role="alert">
|
||||
<ul class="mb-0 mb-lc-0"></ul>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div id="iup-sync-progress-bar" class="progress-bar progress-bar-animated progress-bar-striped" role="progressbar" style="width: <?php echo $stats['pcnt_complete']; ?>%;" aria-valuenow="<?php echo $stats['pcnt_complete']; ?>" aria-valuemin="0"
|
||||
aria-valuemax="100"><?php echo $stats['pcnt_complete']; ?>%
|
||||
</div>
|
||||
</div>
|
||||
<div class="col text-center text-muted">
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">Uploading...</span>
|
||||
</div>
|
||||
<span class="h6" id="iup-upload-progress"><?php printf( __( '<span id="iup-progress-size">%s</span> / <span id="iup-progress-files">%s</span> File(s) Remaining', 'infinite-uploads' ), $stats['remaining_size'], $stats['remaining_files'] ); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col text-center">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/progress-bar-4.svg', dirname( __FILE__ ) ) ); ?>" alt="Progress steps bar" height="19" width="110"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,124 +0,0 @@
|
||||
<div class="card mt-0">
|
||||
<div class="card-header h5">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="m-0 mr-auto p-0"><?php esc_html_e( 'Account & Settings', 'infinite-uploads' ); ?> <span class="dashicons dashicons-info text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Includes usage data for all connected sites', 'infinite-uploads' ); ?>"></span></h5>
|
||||
<span class="m-0 p-0 text-muted iup-refresh-icon">
|
||||
<div class="spinner-grow spinner-grow-sm text-secondary text-hide" role="status">
|
||||
<span class="sr-only">Refreshing...</span>
|
||||
</div>
|
||||
<span class="dashicons dashicons-update-alt mr-1" role="button" data-target="<?php echo esc_url( $this->settings_url( [ 'refresh' => 1 ] ) ); ?>" data-toggle="tooltip" title="<?php esc_attr_e( 'Refresh account data', 'infinite-uploads' ); ?>"></span>
|
||||
<small><?php printf( esc_html__( 'Updated %s ago', 'infinite-uploads' ), human_time_diff( $api_data->refreshed ) ); ?></small>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-md-5">
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h5><?php esc_html_e( 'Infinite Uploads Plan', 'infinite-uploads' ); ?></h5>
|
||||
<p class="lead"><?php esc_html_e( 'Your current Infinite Uploads plan and storage.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col"><?php esc_html_e( 'Used / Available', 'infinite-uploads' ); ?> <span class="dashicons dashicons-info text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Recalculated every 24 hours', 'infinite-uploads' ); ?>"></span></div>
|
||||
<div class="col text-right"><?php esc_html_e( 'Need more?', 'infinite-uploads' ); ?> <a href="<?php echo esc_url( $this->api_url( '/account/billing/' ) ); ?>" class="text-warning"><?php esc_html_e( 'Switch to a new plan.', 'infinite-uploads' ); ?></a></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col badge badge-pill badge-light text-left p-3">
|
||||
<p class="h5 ml-2 mb-0 d-none d-md-block"><?php printf( esc_html__( '%s / %s', 'infinite-uploads' ), $this->size_format_zero( $cloud_total_size, 2 ), esc_html( $api_data->plan->label ) ); ?></p>
|
||||
<p class="h6 ml-2 mb-0 d-md-none"><?php printf( esc_html__( '%s / %s', 'infinite-uploads' ), $this->size_format_zero( $cloud_total_size, 2 ), esc_html( $api_data->plan->label ) ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h5><?php esc_html_e( 'CDN Bandwidth', 'infinite-uploads' ); ?></h5>
|
||||
<p class="lead"><?php esc_html_e( 'Infinite Uploads includes allotted bandwidth for CDN delivery of your files.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col"><?php esc_html_e( 'Used / Available', 'infinite-uploads' ); ?> <span class="dashicons dashicons-info text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Recalculated every 24 hours', 'infinite-uploads' ); ?>"></span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col badge badge-pill badge-light text-left p-3">
|
||||
<p class="h5 ml-2 mb-0"><?php printf( esc_html__( '%s / %s', 'infinite-uploads' ), $this->size_format_zero( $api_data->stats->cloud->bandwidth, 2 ), $this->size_format_zero( $api_data->plan->bandwidth_limit ) ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h5><?php esc_html_e( 'CDN URL', 'infinite-uploads' ); ?></h5>
|
||||
<p class="lead"><?php esc_html_e( 'Your uploads are served from this CDN url via 45+ edge locations around the world.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col"><?php esc_html_e( 'Current CDN URL', 'infinite-uploads' ); ?></div>
|
||||
<?php if ( $api_data->site->cname == $api_data->site->cdn_url ) { ?>
|
||||
<div class="col text-right"><a href="<?php echo esc_url( $this->api_url( '/account/sites/?site=' . $this->api->get_site_id() ) ); ?>#custom-cdn-domain" class="text-warning"><?php esc_html_e( 'Use your own custom domain!', 'infinite-uploads' ); ?></a></div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col badge badge-pill badge-light text-left p-3">
|
||||
<p class="h5 ml-2 mb-0 d-none d-md-block"><?php echo esc_html( $api_data->site->cdn_url ); ?></p>
|
||||
<p class="h6 ml-2 mb-0 d-md-none"><?php echo esc_html( $api_data->site->cdn_url ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h5><?php esc_html_e( 'Storage Region', 'infinite-uploads' ); ?></h5>
|
||||
<p class="lead"><?php esc_html_e( 'The location of our servers storing your uploads.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col"><?php esc_html_e( 'Region', 'infinite-uploads' ); ?> <span class="dashicons dashicons-info text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Region can only be selected when first connecting your site.', 'infinite-uploads' ); ?>"></span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col badge badge-pill badge-light text-left p-3">
|
||||
<p class="h5 ml-2 mb-0"><?php echo esc_html( $region_labels[ $api_data->site->region ] ); ?></p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ( infinite_uploads_enabled() && (bool) $stats['deletable_files'] ) { ?>
|
||||
<div class="row justify-content-center iup-settings-row-delete">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h5><?php esc_html_e( 'Free Up Local Storage', 'infinite-uploads' ); ?></h5>
|
||||
<p class="lead"><?php esc_html_e( 'There are unused local copies of files already synced to the cloud. You can optionally delete these to free up local storage space.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 mt-4">
|
||||
<div class="row text-center mb-3">
|
||||
<div class="col"><?php esc_html_e( 'This saves space and improves server performance.', 'infinite-uploads' ); ?></div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-xl-5 col-lg-6 col-md-7 text-center">
|
||||
<button class="btn text-nowrap btn-info btn-lg btn-block" data-toggle="modal" data-target="#delete-modal"><?php esc_html_e( 'Delete', 'infinite-uploads' ); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col text-center">
|
||||
<p><strong><?php printf( esc_html__( '%s / %s deletable files', 'infinite-uploads' ), $stats['deletable_size'], $stats['deletable_files'] ); ?></strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="row justify-content-center" id="iup-disconnect">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h5><?php esc_html_e( 'Download & Disconnect', 'infinite-uploads' ); ?></h5>
|
||||
<p class="lead"><?php printf( __( 'Download your media files and disconnect from our cloud. To cancel or manage your storage plan please visit <a href="%s" class="text-warning">account management</a>.', 'infinite-uploads' ), esc_url( $this->api_url( '/account/billing/' ) ) ); ?></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 mt-4">
|
||||
<div class="row text-center mb-3">
|
||||
<div class="col"><?php esc_html_e( 'We will download your files back to the uploads directory before disconnecting to prevent broken media on your site.', 'infinite-uploads' ); ?></div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-xl-5 col-lg-6 col-md-7 text-center">
|
||||
<button class="btn text-nowrap btn-info btn-lg btn-block" data-toggle="modal" data-target="#scan-remote-modal" data-next="download"><?php esc_html_e( 'Disconnect', 'infinite-uploads' ); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,12 +0,0 @@
|
||||
<span class="m-0 p-0 text-muted iup-enabled-status">
|
||||
<?php esc_html_e( 'Status', 'infinite-uploads' ); ?>
|
||||
<?php if ( isset( $api_data->site ) && ! $api_data->site->upload_writeable ) { ?>
|
||||
<span class="dashicons dashicons-cloud text-warning" data-toggle="tooltip" title="<?php esc_attr_e( 'There is a problem with your Infinite Uploads account', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } elseif ( infinite_uploads_enabled() ) { ?>
|
||||
<span class="dashicons dashicons-cloud-saved" data-toggle="tooltip" title="<?php esc_attr_e( 'Enabled - new uploads are moved to the cloud', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } elseif ( $this->api->has_token() ) { ?>
|
||||
<span class="dashicons dashicons-cloud-upload text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Disabled - waiting to sync media to the cloud', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } else { ?>
|
||||
<span class="dashicons dashicons-cloud text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Disabled - waiting to connect', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } ?>
|
||||
</span>
|
@ -1,70 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="m-0 mr-auto p-0"><?php esc_html_e( 'Cloud Storage & CDN', 'infinite-uploads' ); ?></h5>
|
||||
<?php require_once( dirname( __FILE__ ) . '/status-icon.php' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body cloud p-md-3">
|
||||
<div class="row align-items-center justify-content-center mb-5">
|
||||
<div class="col-lg col-xs-12">
|
||||
<p class="lead mb-0"><?php esc_html_e( "Total Local Bytes / Files", 'infinite-uploads' ); ?></p>
|
||||
<span class="h2 text-nowrap"><?php echo $stats['local_size']; ?><small class="text-muted"> / <?php echo $stats['local_files']; ?></small></span>
|
||||
|
||||
<div class="container p-0 ml-md-3">
|
||||
<?php foreach ( $this->iup_instance->get_filetypes( false ) as $type ) { ?>
|
||||
<div class="row mt-2">
|
||||
<div class="col-1"><span class="badge badge-pill" style="background-color: <?php echo $type->color; ?>"> </span></div>
|
||||
<div class="col-4 lead text-nowrap"><?php echo $type->label; ?></div>
|
||||
<div class="col-5 text-nowrap"><strong><?php echo size_format( $type->size, 2 ); ?> / <?php echo number_format_i18n( $type->files ); ?></strong></div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="row mt-2">
|
||||
<div class="col text-muted"><small><?php printf( esc_html__( 'Scanned %s ago', 'infinite-uploads' ), human_time_diff( $stats['files_finished'] ) ); ?> ‐ <a href="#" class="badge badge-primary" data-toggle="modal" data-target="#scan-modal"><span
|
||||
data-toggle="tooltip"
|
||||
title="<?php esc_attr_e( 'Run a new scan to detect and sync recently uploaded files.', 'infinite-uploads' ); ?>"><?php esc_html_e( 'Refresh', 'infinite-uploads' ); ?></span></a></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg col-xs-12">
|
||||
<?php if ( isset( $api_data->site ) && ! $api_data->site->upload_writeable ) { ?>
|
||||
<div class="row justify-content-center mb-3 mt-4 mt-lg-0">
|
||||
<div class="col text-center">
|
||||
<p class="lead text-warning"><?php esc_html_e( 'Please fix the billing issue with your account to sync your files to the cloud and enable Infinite Uploads.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col text-center">
|
||||
<a class="btn text-nowrap btn-info btn-lg" href="<?php echo esc_url( $this->api_url( '/account/billing/' ) ); ?>" role="button"><?php esc_html_e( 'Account Management', 'infinite-uploads' ); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php } else { ?>
|
||||
<div class="row justify-content-center mb-3 mt-4 mt-lg-0">
|
||||
<div class="col text-center">
|
||||
<img class="mb-4" src="<?php echo esc_url( plugins_url( '/assets/img/iu-logo-blue.svg', dirname( __FILE__ ) ) ); ?>" alt="Push to Cloud" height="76" width="76"/>
|
||||
<p class="lead"><?php printf( esc_html__( 'You have %s of premium storage available!', 'infinite-uploads' ), $this->size_format_zero( ( $api_data->plan->storage_limit * GB_IN_BYTES ) - $api_data->stats->cloud->storage, 2 ) ); ?></p>
|
||||
<p class="lead"><?php esc_html_e( 'Optionally move your media library to the Infinite Uploads cloud and serve via our CDN.', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col text-center">
|
||||
<?php if ( ! empty( $stats['sync_finished'] ) ) { //if sync is finished show enable button ?>
|
||||
<button class="btn text-nowrap btn-primary btn-lg" data-toggle="modal" data-target="#enable-modal"><span class="dashicons dashicons-cloud"></span> <?php esc_html_e( 'Sync Now', 'infinite-uploads' ); ?></button>
|
||||
<?php } elseif ( ! empty( $stats['compare_finished'] ) ) { ?>
|
||||
<button class="btn text-nowrap btn-primary btn-l" data-toggle="modal" data-target="#upload-modal"><span class="dashicons dashicons-cloud"></span> <?php esc_html_e( 'Sync Now', 'infinite-uploads' ); ?></button>
|
||||
<?php } else { ?>
|
||||
<button class="btn text-nowrap btn-primary btn-lg" id="iup-sync-button" data-toggle="modal" data-target="#scan-remote-modal" data-next="upload"><span class="dashicons dashicons-cloud"></span> <?php esc_html_e( 'Sync Now', 'infinite-uploads' ); ?></button>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-1">
|
||||
<div class="col text-center">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/progress-bar-3.svg', dirname( __FILE__ ) ) ); ?>" alt="Progress steps bar" height="19" width="110"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,27 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="m-0 mr-auto p-0"><?php esc_html_e( 'Video Cloud', 'infinite-uploads' ); ?></h5>
|
||||
<?php require_once( dirname( __FILE__ ) . '/video-status-icon.php' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body cloud p-md-3">
|
||||
<div class="row align-items-center justify-content-center mb-3">
|
||||
<div class="col-lg col-xs-12">
|
||||
<div class="row justify-content-center mb-3 mt-2 mt-lg-2">
|
||||
<div class="col text-center">
|
||||
<img class="mb-4" src="<?php echo esc_url( plugins_url( '/assets/img/video-player.jpg', dirname( __FILE__ ) ) ); ?>" alt="Video player example" height="180" width="320"/>
|
||||
<p class="lead"><?php esc_html_e( "Easily upload videos of any size directly to the cloud and have them automatically transcoded into multiple resolutions for optimal playback on any device. Plus, our customizable unbranded embedded video player allows you to stream your videos from our global CDN, ensuring smooth and seamless playback for your audience. With Infinite Uploads, you'll have everything you need to host and share your videos with the world. All right inside the WordPress dashboard!", 'infinite-uploads' ); ?></p>
|
||||
<p class="lead font-weight-bold"><?php esc_html_e( 'Included FREE with your Infinite Uploads storage plan!', 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col text-center">
|
||||
<button class="btn text-nowrap btn-primary btn-lg" id="iup-enable-video-button"><span class="dashicons dashicons-video-alt3"></span> <?php esc_html_e( 'Enable Video Cloud', 'infinite-uploads' ); ?></button>
|
||||
<div class="spinner-grow text-muted d-none mx-auto" id="iup-enable-video-spinner" role="status"><span class="sr-only"><?php esc_html_e( 'Enabling...', 'iup' ); ?></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,46 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="m-0 mr-auto p-0"><?php esc_html_e( 'Video Cloud Overview', 'infinite-uploads' ); ?></h5>
|
||||
<?php require_once( dirname( __FILE__ ) . '/video-status-icon.php' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body cloud p-md-4">
|
||||
<div class="row align-items-center justify-content-between mb-1">
|
||||
<div class="col">
|
||||
<h6>
|
||||
<?php esc_html_e( 'Site stats:', 'infinite-uploads' ); ?> <span class="dashicons dashicons-info text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Recalculated every 24 hours', 'infinite-uploads' ); ?>"></span>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row align-items-center justify-content-between mb-5">
|
||||
<div class="col-lg col-xs-12 mx-sm-0">
|
||||
<p class="lead mb-0"><?php esc_html_e( "Video Count", 'infinite-uploads' ); ?></p>
|
||||
<span class="h2 text-nowrap"><?php echo number_format_i18n( $video_library_settings->VideoCount ?? 0 ); ?></span>
|
||||
</div>
|
||||
<div class="col-lg col-xs-12 mx-sm-0">
|
||||
<p class="lead mb-0"><?php esc_html_e( "Video Storage", 'infinite-uploads' ); ?></p>
|
||||
<span class="h2 text-nowrap"><?php echo $this->size_format_zero( $video_library_settings->StorageUsage ?? 0, 2 ); ?></span>
|
||||
</div>
|
||||
<div class="col-lg col-xs-12 mx-sm-0">
|
||||
<p class="lead mb-0"><?php esc_html_e( "Video Bandwidth", 'infinite-uploads' ); ?> <span class="dashicons dashicons-info text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'This calendar month.', 'infinite-uploads' ); ?>"></span></p>
|
||||
<span class="h2 text-nowrap"><?php echo $this->size_format_zero( $video_library_settings->TrafficUsage ?? 0, 2 ); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="col text-center">
|
||||
<p class="lead"><?php esc_html_e( "Upload, transcode, and embed videos of any size via our Gutenberg Block, shortcode, or video library. Included FREE with your Infinite Uploads storage & CDN plan.", 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-1">
|
||||
<div class="col text-center">
|
||||
<p><?php esc_html_e( 'View and manage your cloud video library.', 'infinite-uploads' ); ?></p>
|
||||
<a class="btn text-nowrap btn-primary btn-lg" href="<?php echo esc_url( $this->video->library_url() ); ?>" role="button"><span class="dashicons dashicons-embed-video"></span> <?php esc_html_e( 'Video Library', 'infinite-uploads' ); ?></a>
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<p><?php esc_html_e( 'Manage cloud video library settings.', 'infinite-uploads' ); ?></p>
|
||||
<a class="btn text-nowrap btn-info btn-lg" href="<?php echo esc_url( $this->video->settings_url() ); ?>" role="button"><span class="dashicons dashicons-admin-generic"></span> <?php esc_html_e( 'Settings', 'infinite-uploads' ); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,12 +0,0 @@
|
||||
<span class="m-0 p-0 text-muted iup-enabled-status">
|
||||
<?php esc_html_e( 'Status', 'infinite-uploads' ); ?>
|
||||
<?php if ( $this->video->is_video_active() && ! $this->video->is_video_enabled() ) { ?>
|
||||
<span class="dashicons dashicons-video-alt3 text-warning" data-toggle="tooltip" title="<?php esc_attr_e( 'There is a problem with your Infinite Uploads account', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } elseif ( $this->video->is_video_active() ) { ?>
|
||||
<span class="dashicons dashicons-video-alt3 " data-toggle="tooltip" title="<?php esc_attr_e( 'Enabled', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } elseif ( $this->api->has_token() ) { ?>
|
||||
<span class="dashicons dashicons-video-alt3 text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Disabled', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } else { ?>
|
||||
<span class="dashicons dashicons-video-alt3 text-muted" data-toggle="tooltip" title="<?php esc_attr_e( 'Disabled - waiting to connect', 'infinite-uploads' ); ?>"></span>
|
||||
<?php } ?>
|
||||
</span>
|
@ -1,21 +0,0 @@
|
||||
<div class="card">
|
||||
<div class="card-body cloud p-md-5">
|
||||
<div class="row justify-content-center mb-5 mt-3">
|
||||
<div class="col text-center">
|
||||
<img class="mb-4" src="<?php echo esc_url( plugins_url( '/assets/img/iu-logo-blue.svg', dirname( __FILE__ ) ) ); ?>" alt="Push to Cloud" height="76" width="76"/>
|
||||
<h4><?php esc_html_e( 'Infinite Uploads Setup', 'infinite-uploads' ); ?></h4>
|
||||
<p class="lead"><?php esc_html_e( "Welcome to Infinite Uploads, scalable cloud storage, encoding, and delivery for your uploads and videos made easy! Get started with a scan of your existing Media Library. Then our smart recommendations will help you chose the best plan, create or connect your account, and voilà – you're ready to push to the cloud.", 'infinite-uploads' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-5">
|
||||
<div class="col text-center">
|
||||
<button class="btn text-nowrap btn-primary btn-lg" data-toggle="modal" data-target="#scan-modal"><?php esc_html_e( 'Run Scan', 'infinite-uploads' ); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-1">
|
||||
<div class="col text-center">
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/progress-bar-0.svg', dirname( __FILE__ ) ) ); ?>" alt="Progress steps bar" height="19" width="110"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,31 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useEffect, useState} from '@wordpress/element';
|
||||
import Button from "react-bootstrap/Button";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import {ChromePicker} from "react-color";
|
||||
|
||||
export default function ColorPick({settings, setSettings}) {
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
|
||||
return (
|
||||
<Row className="justify-content-start">
|
||||
<Col xs={2}>
|
||||
<Button style={{backgroundColor: settings.PlayerKeyColor}} variant="secondary" className="rounded-pill px-4" onClick={() => {
|
||||
setShowPicker(!showPicker);
|
||||
}}>{settings.PlayerKeyColor}</Button>
|
||||
</Col>
|
||||
{showPicker && (
|
||||
<Col>
|
||||
<ChromePicker
|
||||
color={settings.PlayerKeyColor}
|
||||
onChangeComplete={(color) => {
|
||||
setSettings({...settings, PlayerKeyColor: color.hex});
|
||||
}}
|
||||
disableAlpha
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useState, useEffect} from '@wordpress/element';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
|
||||
export default function DeleteModal({video, setVideos}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const handleClose = () => setShow(false);
|
||||
const handleShow = () => setShow(true);
|
||||
|
||||
function deleteVideo() {
|
||||
const formData = new FormData();
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-delete`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
if (data.success) {
|
||||
setVideos((videos) =>
|
||||
videos.filter((v) => v.guid !== video.guid)
|
||||
);
|
||||
handleClose();
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="outline-danger"
|
||||
size="sm"
|
||||
onClick={handleShow}
|
||||
className="rounded-4"
|
||||
>
|
||||
{__('Delete Video', 'infinite-uploads')}
|
||||
</Button>
|
||||
|
||||
<Modal show={show} onHide={handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
{__('Delete Video:', 'infinite-uploads')}{' '}
|
||||
{video.title}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{__(
|
||||
'Are you sure you would like to delete this video?',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleClose}>
|
||||
{__('Cancel', 'infinite-uploads')}
|
||||
</Button>
|
||||
<Button variant="danger" onClick={deleteVideo}>
|
||||
{__('Delete', 'infinite-uploads')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useState} from '@wordpress/element';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import InputGroup from 'react-bootstrap/InputGroup';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import {VideoSize} from "./VideoAttributes";
|
||||
import UploadModal from "./UploadModal";
|
||||
|
||||
function Header({orderBy, setOrderBy, search, setSearch, selectVideo, getVideos}) {
|
||||
|
||||
const sizeOf = function (bytes) {
|
||||
if (bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
var e = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (
|
||||
(bytes / Math.pow(1024, e)).toFixed(1) +
|
||||
' ' +
|
||||
' KMGTP'.charAt(e) +
|
||||
'B'
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row className="align-items-center">
|
||||
<Col sm={8} md={3} className="mb-3 mb-lg-0">
|
||||
<InputGroup>
|
||||
<InputGroup.Text>
|
||||
<span className="dashicons dashicons-search"></span>
|
||||
</InputGroup.Text>
|
||||
<Form.Control
|
||||
placeholder={__('Search', 'infinite-uploads')}
|
||||
aria-label={__('Search', 'infinite-uploads')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col sm={4} md={2} className="mb-3 mb-lg-0">
|
||||
<InputGroup>
|
||||
<InputGroup.Text>
|
||||
{__('Sort', 'infinite-uploads')}
|
||||
</InputGroup.Text>
|
||||
<Form.Select
|
||||
aria-label={__(
|
||||
'Sort by select',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
value={orderBy}
|
||||
onChange={(e) => setOrderBy(e.target.value)}
|
||||
>
|
||||
>
|
||||
<option value="title">
|
||||
{__('Title', 'infinite-uploads')}
|
||||
</option>
|
||||
<option value="date">
|
||||
{__('Date', 'infinite-uploads')}
|
||||
</option>
|
||||
</Form.Select>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col className="mb-3 mb-lg-0">
|
||||
<Row className="justify-content-center flex-nowrap">
|
||||
<Col className="col-auto">
|
||||
<p className="mb-0">{__("Video Count", 'infinite-uploads')}</p>
|
||||
<span className="h4 text-nowrap">{IUP_VIDEO.settings.VideoCount}</span>
|
||||
</Col>
|
||||
<Col className="col-auto">
|
||||
<p className="mb-0">{__("Library Storage", 'infinite-uploads')}</p>
|
||||
<span className="h4 text-nowrap">{sizeOf(IUP_VIDEO.settings.StorageUsage)}</span>
|
||||
</Col>
|
||||
<Col className="col-auto">
|
||||
<p className="mb-0">{__("Video Bandwidth", 'infinite-uploads')}</p>
|
||||
<span className="h4 text-nowrap">{sizeOf(IUP_VIDEO.settings.TrafficUsage)}</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="d-flex justify-content-end mb-3 mb-lg-0">
|
||||
<Button
|
||||
variant="outline-secondary"
|
||||
className="rounded-pill text-nowrap"
|
||||
href={IUP_VIDEO.settingsUrl}
|
||||
>
|
||||
<span className="dashicons dashicons-admin-generic"></span>
|
||||
{__('Settings', 'infinite-uploads')}
|
||||
</Button>
|
||||
{!selectVideo && (
|
||||
<UploadModal getVideos={getVideos}/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
@ -1,158 +0,0 @@
|
||||
import {useEffect, useState} from '@wordpress/element';
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import VideoCard from './VideoCard';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Header from './Header';
|
||||
import Paginator from './Paginator';
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
|
||||
export default function Library({selectVideo}) {
|
||||
const [videos, setVideos] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [orderBy, setOrderBy] = useState('date');
|
||||
const [search, setSearch] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalItems, setTotalItems] = useState(0);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(40);
|
||||
const [refreshInterval, setRefreshInterval] = useState(60000);
|
||||
|
||||
//get videos on render
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setLoading(true);
|
||||
getVideos();
|
||||
}
|
||||
}, [orderBy, page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (search.length > 2 || search.length === 0) {
|
||||
setPage(1);
|
||||
setLoading(true);
|
||||
getVideos();
|
||||
}
|
||||
}, [search]);
|
||||
|
||||
useEffect(() => {
|
||||
//check the videos array if any of the video objects are currently processing or transcoding
|
||||
const processing = videos.find((video) => (video.status === 2 || video.status === 3));
|
||||
if (processing) {
|
||||
setRefreshInterval(10000);
|
||||
} else {
|
||||
setRefreshInterval(60000);
|
||||
}
|
||||
}, [videos]);
|
||||
|
||||
//fetch videos on a 30s interval
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
getVideos();
|
||||
}, refreshInterval);
|
||||
return () => clearInterval(interval);
|
||||
}, [orderBy, page, search, refreshInterval]);
|
||||
|
||||
function getVideos() {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
AccessKey: IUP_VIDEO.apiKey,
|
||||
},
|
||||
};
|
||||
|
||||
fetch(
|
||||
`https://video.bunnycdn.com/library/${IUP_VIDEO.libraryId}/videos?page=${page}&itemsPerPage=${itemsPerPage}&orderBy=${orderBy}&search=${search}`,
|
||||
options
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log('Videos:', data);
|
||||
setVideos(data.items);
|
||||
setTotalItems(data.totalItems);
|
||||
setItemsPerPage(data.itemsPerPage);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!selectVideo && (
|
||||
<h1 className="text-muted mb-3">
|
||||
<img
|
||||
src={IUP_VIDEO.assetBase + '/img/iu-logo-gray.svg'}
|
||||
alt="Infinite Uploads Logo"
|
||||
height="32"
|
||||
width="32"
|
||||
className="me-2"
|
||||
/>
|
||||
{__('Cloud Video Library', 'infinite-uploads')}
|
||||
</h1>
|
||||
)}
|
||||
<Container fluid>
|
||||
<Header
|
||||
{...{
|
||||
orderBy,
|
||||
setOrderBy,
|
||||
search,
|
||||
setSearch,
|
||||
selectVideo,
|
||||
getVideos
|
||||
}}
|
||||
/>
|
||||
|
||||
{!loading ? (
|
||||
<Container fluid>
|
||||
<Row
|
||||
xs={1}
|
||||
sm={1}
|
||||
md={2}
|
||||
lg={3}
|
||||
xl={4}
|
||||
xxl={5}
|
||||
>
|
||||
{videos.length > 0 ? (
|
||||
videos.map((video, index) => {
|
||||
return (
|
||||
<Col key={index + video.guid}>
|
||||
<VideoCard
|
||||
{...{
|
||||
video,
|
||||
setVideos,
|
||||
selectVideo,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Container className="my-5 justify-content-center align-items-center">
|
||||
<p className="text-muted text-center h5">
|
||||
{__('No videos found.', 'infinite-uploads')}
|
||||
</p>
|
||||
</Container>
|
||||
)}
|
||||
</Row>
|
||||
<Paginator
|
||||
{...{page, setPage, totalItems, itemsPerPage}}
|
||||
/>
|
||||
</Container>
|
||||
) : (
|
||||
<Container className="d-flex justify-content-center align-middle my-5">
|
||||
<Spinner
|
||||
animation="grow"
|
||||
role="status"
|
||||
className="my-5"
|
||||
>
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import Pagination from 'react-bootstrap/Pagination';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
|
||||
function Paginator({page, setPage, totalItems, itemsPerPage}) {
|
||||
if (totalItems <= itemsPerPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let active = page;
|
||||
const lastPage = Math.ceil(totalItems / itemsPerPage);
|
||||
let items = [];
|
||||
for (let number = 1; number <= lastPage; number++) {
|
||||
items.push(
|
||||
<Pagination.Item
|
||||
key={number}
|
||||
active={number === active}
|
||||
onClick={() => setPage(number)}
|
||||
>
|
||||
{number}
|
||||
</Pagination.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Pagination className="justify-content-center mt-4">
|
||||
<Pagination.First
|
||||
onClick={() => setPage(1)}
|
||||
disabled={page === 1}
|
||||
/>
|
||||
{items}
|
||||
<Pagination.Last
|
||||
onClick={() => setPage(lastPage)}
|
||||
disabled={page === lastPage}
|
||||
/>
|
||||
</Pagination>
|
||||
);
|
||||
}
|
||||
|
||||
export default Paginator;
|
@ -1,27 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
|
||||
export default function PlayerCheckbox({control, icon, label, settings, setSettings}) {
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
id={control}
|
||||
inline
|
||||
label={
|
||||
<span className="text-nowrap"><span className={"dashicons dashicons-" + icon}></span> {label}</span>
|
||||
}
|
||||
checked={settings.Controls.includes(control)}
|
||||
onChange={e => setSettings(settings => {
|
||||
if (e.target.checked) {
|
||||
return {...settings, Controls: [...settings.Controls, control]};
|
||||
} else {
|
||||
return {...settings, Controls: settings.Controls.filter(ctrl => ctrl !== control)};
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import Row from "react-bootstrap/Row";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import {Badge} from "react-bootstrap";
|
||||
|
||||
export default function ResCheckbox({px, bitrate, settings, setSettings}) {
|
||||
const resolution = px + "p";
|
||||
//calculate width if 16:9 based on height
|
||||
const width = Math.round(px * 16 / 9);
|
||||
const label = "(" + width + "x" + px + ")";
|
||||
const bitrateLabel = bitrate ? bitrate + " kbps" : "";
|
||||
const hdLabel = px >= 2160 ? "4K UHD" : px >= 1440 ? "2K QHD" : px >= 1080 ? "Full HD" : px >= 720 ? "HD" : "";
|
||||
const disabled = px >= 1440;
|
||||
|
||||
return (
|
||||
<Badge bg="light" text="dark" className="mb-2 rounded-pill px-3 w-100">
|
||||
<Row className="d-flex justify-content-between align-items-center">
|
||||
<Col className="">
|
||||
<Form.Check
|
||||
className="d-flex align-items-center"
|
||||
type="checkbox"
|
||||
id={resolution}
|
||||
label={
|
||||
<span className="text-nowrap"><strong>{resolution}</strong> {label} {hdLabel}</span>
|
||||
}
|
||||
checked={settings.EnabledResolutions.includes(resolution)}
|
||||
onChange={e => setSettings(settings => {
|
||||
if (e.target.checked) {
|
||||
return {...settings, EnabledResolutions: [...settings.EnabledResolutions, resolution]};
|
||||
} else {
|
||||
return {...settings, EnabledResolutions: settings.EnabledResolutions.filter(res => res !== resolution)};
|
||||
}
|
||||
})}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="col-auto">
|
||||
{bitrateLabel}
|
||||
</Col>
|
||||
</Row>
|
||||
</Badge>
|
||||
);
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
import {useEffect, useState} from '@wordpress/element';
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Card from "react-bootstrap/Card";
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import PlayerCheckbox from "./PlayerCheckbox";
|
||||
import ResCheckbox from "./ResCheckbox";
|
||||
import ColorPick from "./ColorPick";
|
||||
import Tabs from "react-bootstrap/Tabs";
|
||||
import Tab from "react-bootstrap/Tab";
|
||||
import Button from "react-bootstrap/Button";
|
||||
|
||||
export default function Settings() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [settings, setSettings] = useState(IUP_VIDEO.settings);
|
||||
|
||||
function updateSettings() {
|
||||
setLoading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('settings', JSON.stringify(settings));
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-settings`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setSettings(data.data);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (!settings) {
|
||||
return (
|
||||
<h2>{__('Video library not yet connected.', 'infinite-uploads')}</h2>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container fluid>
|
||||
<Row className="justify-content-between align-items-center">
|
||||
<Col>
|
||||
<h1 className="text-muted mb-3">
|
||||
<img
|
||||
src={IUP_VIDEO.assetBase + '/img/iu-logo-gray.svg'}
|
||||
alt="Infinite Uploads Logo"
|
||||
height="32"
|
||||
width="32"
|
||||
className="me-2"
|
||||
/>
|
||||
{__('Infinite Uploads Video Settings', 'infinite-uploads')}
|
||||
</h1>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button variant="primary" className="float-end" href={IUP_VIDEO.libraryUrl}>
|
||||
{__('Video Library', 'infinite-uploads')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<Tabs
|
||||
defaultActiveKey="player"
|
||||
id="video-settings-tabs"
|
||||
className="mb-3"
|
||||
>
|
||||
<Tab eventKey="player" title={__('Player', 'infinite-uploads')} className="mt-4">
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Main Player Color', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the primary color that will be displayed for the controls in the video player.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col>
|
||||
<ColorPick {...{settings, setSettings}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Player Language', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the default language that will be displayed in the video player.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col>
|
||||
<Form.Select size="lg" value={settings.UILanguage} onChange={(e) => setSettings({...settings, UILanguage: e.target.value})}>
|
||||
<option value="en" label="English"></option>
|
||||
<option value="ar" label="Arabic"></option>
|
||||
<option value="bu" label="Bulgarian"></option>
|
||||
<option value="cn" label="Chinese"></option>
|
||||
<option value="cz" label="Czech"></option>
|
||||
<option value="dk" label="Danish"></option>
|
||||
<option value="nl" label="Dutch"></option>
|
||||
<option value="fi" label="Finnish"></option>
|
||||
<option value="fr" label="French"></option>
|
||||
<option value="de" label="German"></option>
|
||||
<option value="gr" label="Greek"></option>
|
||||
<option value="hu" label="Hungarian"></option>
|
||||
<option value="id" label="Indonesian"></option>
|
||||
<option value="it" label="Italian"></option>
|
||||
<option value="jp" label="Japanese"></option>
|
||||
<option value="kr" label="Korean"></option>
|
||||
<option value="no" label="Norwegian"></option>
|
||||
<option value="pl" label="Polish"></option>
|
||||
<option value="pt" label="Portuguese"></option>
|
||||
<option value="ro" label="Romanian"></option>
|
||||
<option value="rs" label="Serbian"></option>
|
||||
<option value="sk" label="Slovakian"></option>
|
||||
<option value="si" label="Slovenian"></option>
|
||||
<option value="es" label="Spanish"></option>
|
||||
<option value="se" label="Swedish"></option>
|
||||
<option value="ru" label="Russian"></option>
|
||||
<option value="th" label="Thai"></option>
|
||||
<option value="tr" label="Turkish"></option>
|
||||
<option value="ua" label="Ukrainian"></option>
|
||||
<option value="vn" label="Vietnamese"></option>
|
||||
</Form.Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Player Controls', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the UI controls that will be displayed on the player.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col className="d-flex flex-wrap justify-content-between">
|
||||
<Row>
|
||||
<PlayerCheckbox control="play" icon="controls-play" label={__('Play / Pause', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="play-large" icon="video-alt3" label={__('Center Play Button', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="volume" icon="controls-volumeon" label={__('Volume', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="mute" icon="controls-volumeoff" label={__('Mute', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="pip" icon="external" label={__('Picture-in-Picture', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="settings" icon="admin-generic" label={__('Settings', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="captions" icon="format-status" label={__('Captions', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="current-time" icon="clock" label={__('Current Time', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="duration" icon="editor-video" label={__('Duration', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="rewind" icon="controls-skipback" label={__('10s Backward', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="fast-forward" icon="controls-skipforward" label={__('10s Forward', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="progress" icon="leftright" label={__('Progress Bar', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="fullscreen" icon="fullscreen-alt" label={__('Full Screen', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
<Tab eventKey="encoding" title={__('Encoding', 'infinite-uploads')} className="mt-4">
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Enabled Resolutions', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the enabled resolutions that will be encoded on upload. More resolutions provide a more efficient streaming service to users, but require more storage space. Resolutions larger than the original video will be skipped.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col>
|
||||
<ResCheckbox {...{settings, setSettings}} px={240} bitrate={600}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={360} bitrate={800}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={480} bitrate={1400}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={720} bitrate={2800}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={1080} bitrate={5000}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={1440} bitrate={8000}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={2160} bitrate={25000}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Row className="justify-content-center mb-3">
|
||||
<Col className="text-center">
|
||||
<Button variant="info" className="text-nowrap text-white px-4" onClick={updateSettings} disabled={loading}>{__('Save Settings', 'infinite-uploads')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
|
||||
</Container>
|
||||
);
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useState, useEffect, useRef} from '@wordpress/element';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Uppy from '@uppy/core';
|
||||
import Tus from '@uppy/tus';
|
||||
import {DragDrop, StatusBar, useUppy} from '@uppy/react';
|
||||
import UppyCreateVid from '../../block/edit-uppy-plugin';
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/drag-drop/dist/style.css';
|
||||
import '@uppy/status-bar/dist/style.css';
|
||||
|
||||
export default function UploadModal({getVideos}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const uploadAuth = useRef(null);
|
||||
const uploaded = useRef({});
|
||||
|
||||
const handleShow = () => {
|
||||
setShow(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
const uppy = useUppy(() => {
|
||||
return new Uppy({
|
||||
debug: true,
|
||||
restrictions: {
|
||||
maxNumberOfFiles: null,
|
||||
allowedFileTypes: ['video/*'],
|
||||
},
|
||||
autoProceed: true,
|
||||
allowMultipleUploadBatches: true,
|
||||
onBeforeUpload: (files) => {
|
||||
//TODO trigger error if video_id is null
|
||||
},
|
||||
})
|
||||
.use(Tus, {
|
||||
endpoint: 'https://video.bunnycdn.com/tusupload',
|
||||
retryDelays: [0, 1000, 3000, 5000, 10000],
|
||||
onBeforeRequest: (req, file) => {
|
||||
//console.log('Video Auth:', uploadAuth.current[file.id]);
|
||||
if (!uploadAuth.current[file.id]) {
|
||||
throw new Error('Error fetching auth.');
|
||||
return false;
|
||||
}
|
||||
|
||||
req.setHeader(
|
||||
'AuthorizationSignature',
|
||||
uploadAuth.current[file.id].AuthorizationSignature
|
||||
);
|
||||
req.setHeader(
|
||||
'AuthorizationExpire',
|
||||
uploadAuth.current[file.id].AuthorizationExpire
|
||||
);
|
||||
req.setHeader('VideoId', uploadAuth.current[file.id].VideoId);
|
||||
req.setHeader('LibraryId', IUP_VIDEO.libraryId);
|
||||
},
|
||||
})
|
||||
.use(UppyCreateVid, {uploadAuth}); //our custom plugin
|
||||
});
|
||||
|
||||
uppy.on('error', (error) => {
|
||||
console.error(error.stack);
|
||||
});
|
||||
uppy.on('upload-error', (file, error, response) => {
|
||||
console.log('error with file:', file.id);
|
||||
console.log('error message:', error);
|
||||
});
|
||||
uppy.on('upload-success', (file, response) => {
|
||||
if (!uploaded.current[file.id]) { //make sure it only triggers once
|
||||
getVideos();
|
||||
uploaded.current = {...uploaded.current, [file.id]: true};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="text-nowrap text-white ms-4"
|
||||
onClick={handleShow}
|
||||
>
|
||||
<span className="dashicons dashicons-video-alt3"></span>
|
||||
{__('Upload Videos', 'infinite-uploads')}
|
||||
</Button>
|
||||
|
||||
<Modal
|
||||
show={show}
|
||||
onHide={handleClose}
|
||||
size="lg"
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
centered
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title id="contained-modal-title-vcenter">
|
||||
{__('Upload Videos', 'infinite-uploads')}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Container fluid className="p-3">
|
||||
|
||||
<div className="uppy-wrapper">
|
||||
<DragDrop
|
||||
width="100%"
|
||||
height="100%"
|
||||
// assuming `props.uppy` contains an Uppy instance:
|
||||
uppy={uppy}
|
||||
locale={{
|
||||
strings: {
|
||||
// Text to show on the droppable area.
|
||||
// `%{browse}` is replaced with a link that opens the system file selection dialog.
|
||||
dropHereOr: __(
|
||||
'Drop videos here or %{browse}.',
|
||||
'infinite-uploads'
|
||||
),
|
||||
// Used as the label for the link that opens the system file selection dialog.
|
||||
browse: __(
|
||||
'browse files',
|
||||
'infinite-uploads'
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<StatusBar
|
||||
// assuming `props.uppy` contains an Uppy instance:
|
||||
uppy={uppy}
|
||||
hideUploadButton={true}
|
||||
hideAfterFinish={true}
|
||||
showProgressDetails
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
|
||||
export function VideoSize({video}) {
|
||||
const sizeOf = function (bytes) {
|
||||
if (bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
var e = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (
|
||||
(bytes / Math.pow(1024, e)).toFixed(1) +
|
||||
' ' +
|
||||
' KMGTP'.charAt(e) +
|
||||
'B'
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('Storage Size', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-media-video me-1"></span>
|
||||
{sizeOf(video.storageSize)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoLength({video}) {
|
||||
//function to turn seconds into a human readable time
|
||||
const secondsToTime = function (seconds) {
|
||||
var h = Math.floor(seconds / 3600);
|
||||
var m = Math.floor((seconds % 3600) / 60);
|
||||
var s = Math.floor((seconds % 3600) % 60);
|
||||
return (
|
||||
(h > 0 ? h + ':' : '') +
|
||||
(m > 0 ? (h > 0 && m < 10 ? '0' : '') + m + ':' : '0:') +
|
||||
(s < 10 ? '0' : '') +
|
||||
s
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('Video Length', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-clock me-1"></span>
|
||||
{secondsToTime(video.length)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoViews({video}) {
|
||||
return (
|
||||
<span
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('View Count', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-welcome-view-site me-1"></span>
|
||||
{video.views}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoDate({video}) {
|
||||
//change datetime to human-readable in localstring
|
||||
const dateTime = function (date) {
|
||||
return new Date(date).toLocaleString();
|
||||
};
|
||||
|
||||
return (
|
||||
<small
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('Upload Date', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-calendar me-1"></span>
|
||||
{dateTime(video.dateUploaded)}
|
||||
</small>
|
||||
);
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import {useState} from '@wordpress/element';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import {VideoLength, VideoViews, VideoSize} from './VideoAttributes';
|
||||
import VideoModal from './VideoModal';
|
||||
import ProgressBar from 'react-bootstrap/ProgressBar';
|
||||
import DeleteModal from './DeleteModal';
|
||||
|
||||
function VideoCard({video, videos, setVideos, selectVideo}) {
|
||||
const getThumbnail = (file) => {
|
||||
return IUP_VIDEO.cdnUrl + '/' + video.guid + '/' + file;
|
||||
};
|
||||
|
||||
const [src, setSrc] = useState(getThumbnail(video.thumbnailFileName));
|
||||
|
||||
const statusLabels = {
|
||||
0: __('Awaiting Upload', 'infinite-uploads'),
|
||||
1: __('Uploaded', 'infinite-uploads'),
|
||||
2: __('Processing', 'infinite-uploads'),
|
||||
3: __('Transcoding', 'infinite-uploads'),
|
||||
4: __('Finished', 'infinite-uploads'),
|
||||
5: __('Error', 'infinite-uploads'),
|
||||
6: __('Upload Failed', 'infinite-uploads'),
|
||||
};
|
||||
const status = statusLabels[video.status];
|
||||
|
||||
if ([0, 1, 5, 6].includes(video.status)) {
|
||||
return (
|
||||
<span className="m-3 w-100 p-0">
|
||||
<Card className="m-0 shadow-sm">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black text-white rounded-top">
|
||||
<div>
|
||||
<div className="d-flex justify-content-center align-items-center h-100 text-secondary font-weight-bold">
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card.Body className={'p-2'}>
|
||||
<Card.Title className="h6 card-title text-truncate">
|
||||
{video.title}
|
||||
</Card.Title>
|
||||
<Row className="justify-content-end text-muted align-items-center">
|
||||
<Col className="justify-content-end d-flex">
|
||||
{!selectVideo && (
|
||||
<DeleteModal
|
||||
video={video}
|
||||
setVideos={setVideos}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</span>
|
||||
);
|
||||
} else if ([2].includes(video.status)) {
|
||||
//processing
|
||||
return (
|
||||
<span className="m-3 w-100 p-0">
|
||||
<Card className="m-0 shadow-sm">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black text-white rounded-top">
|
||||
<div>
|
||||
<div className="d-flex justify-content-center align-items-center h-100 text-secondary font-weight-bold">
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card.Body className={'p-2'}>
|
||||
<Card.Title className="h6 card-title text-truncate">
|
||||
{video.title}
|
||||
</Card.Title>
|
||||
<small className="row justify-content-between text-muted align-items-center">
|
||||
<Col className="col-auto">
|
||||
{__('Processing', 'infinite-uploads')}:
|
||||
</Col>
|
||||
<Col>
|
||||
<ProgressBar
|
||||
animated
|
||||
now={video.encodeProgress}
|
||||
label={`${video.encodeProgress}%`}
|
||||
className="w-100"
|
||||
/>
|
||||
</Col>
|
||||
</small>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<VideoModal {...{video, setVideos, selectVideo}}>
|
||||
<Card className="m-0 shadow-sm">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black rounded-top">
|
||||
<div
|
||||
className="iup-video-thumb"
|
||||
style={{backgroundImage: `url("${src}")`}}
|
||||
onMouseOver={() =>
|
||||
setSrc(getThumbnail('preview.webp'))
|
||||
}
|
||||
onMouseOut={() =>
|
||||
setSrc(
|
||||
getThumbnail(video.thumbnailFileName)
|
||||
)
|
||||
}
|
||||
></div>
|
||||
</div>
|
||||
<Card.Body className={'p-2'}>
|
||||
<Card.Title className="h6 card-title text-truncate">
|
||||
{video.title}
|
||||
</Card.Title>
|
||||
{video.status === 3 ? (
|
||||
<small className="row justify-content-between text-muted align-items-center">
|
||||
<Col className="col-auto">
|
||||
{__('Transcoding', 'infinite-uploads')}:
|
||||
</Col>
|
||||
<Col>
|
||||
<ProgressBar
|
||||
animated
|
||||
now={video.encodeProgress}
|
||||
label={`${video.encodeProgress}%`}
|
||||
className="w-100"
|
||||
/>
|
||||
</Col>
|
||||
</small>
|
||||
) : (
|
||||
<small className="row justify-content-between text-muted align-items-center">
|
||||
<Col>
|
||||
<VideoLength video={video}/>
|
||||
</Col>
|
||||
<Col></Col>
|
||||
<Col>
|
||||
<VideoSize video={video}/>
|
||||
</Col>
|
||||
</small>
|
||||
)}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</VideoModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoCard;
|
@ -1,604 +0,0 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import {useState, useEffect} from '@wordpress/element';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import {
|
||||
VideoLength,
|
||||
VideoSize,
|
||||
VideoViews,
|
||||
VideoDate,
|
||||
} from './VideoAttributes';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import InputGroup from 'react-bootstrap/InputGroup';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Tab from 'react-bootstrap/Tab';
|
||||
import Tabs from 'react-bootstrap/Tabs';
|
||||
import DeleteModal from './DeleteModal';
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
|
||||
export default function VideoModal({
|
||||
video,
|
||||
setVideos,
|
||||
selectVideo,
|
||||
children,
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const [title, setTitle] = useState(video.title);
|
||||
const [autoPlay, setAutoPlay] = useState(false);
|
||||
const [loop, setLoop] = useState(false);
|
||||
const [muted, setMuted] = useState(false);
|
||||
const [preload, setPreload] = useState(true);
|
||||
const [embedParams, setEmbedParams] = useState('');
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [iframe, setIframe] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
let params = [];
|
||||
if (autoPlay) {
|
||||
params.push('autoplay="true"');
|
||||
}
|
||||
if (loop) {
|
||||
params.push('loop="true"');
|
||||
}
|
||||
if (muted) {
|
||||
params.push('muted="true"');
|
||||
}
|
||||
if (preload) {
|
||||
params.push('preload="true"');
|
||||
}
|
||||
setEmbedParams(params.join(' '));
|
||||
}, [autoPlay, loop, muted, preload]);
|
||||
|
||||
useEffect(() => {
|
||||
const component = (
|
||||
<iframe
|
||||
src={`https://iframe.mediadelivery.net/embed/${
|
||||
video.videoLibraryId
|
||||
}/${video.guid}?autoplay=false&v=${Math.random()}`}
|
||||
loading="lazy"
|
||||
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
|
||||
allowFullScreen={true}
|
||||
></iframe>
|
||||
);
|
||||
setIframe(component);
|
||||
}, [video]);
|
||||
|
||||
const getThumbnail = (file) => {
|
||||
return IUP_VIDEO.cdnUrl + '/' + video.guid + '/' + file;
|
||||
};
|
||||
|
||||
const handleShow = () => {
|
||||
setShow(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
function updateVideo() {
|
||||
setLoading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-update`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setVideos((videos) =>
|
||||
videos.map((v) =>
|
||||
v.guid === video.guid ? {...v, title} : v
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function setThumbnail(thumbnailFileName) {
|
||||
setLoading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('thumbnail', thumbnailFileName);
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-update`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setVideos((videos) =>
|
||||
videos.map((v) =>
|
||||
v.guid === video.guid
|
||||
? {...v, thumbnailFileName}
|
||||
: v
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function uploadThumbnail(file) {
|
||||
setUploading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('thumbnailFile', file);
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-update`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
getVideo(); // refresh video data
|
||||
setUploading(false);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
setUploading(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setUploading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function getVideo() {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
AccessKey: IUP_VIDEO.apiKey,
|
||||
},
|
||||
};
|
||||
|
||||
fetch(
|
||||
`https://video.bunnycdn.com/library/${IUP_VIDEO.libraryId}/videos/${video.guid}`,
|
||||
options
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
//replace video in videos array
|
||||
setVideos((videos) =>
|
||||
videos.map((v) =>
|
||||
v.guid === video.guid ? {...v, ...data} : v
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
let thumbnails = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
thumbnails.push(
|
||||
<Col key={i} className="mb-2">
|
||||
<Card
|
||||
className="bg-dark text-white h-100 p-0"
|
||||
role="button"
|
||||
disabled={loading || uploading}
|
||||
onClick={() => setThumbnail('thumbnail_' + i + '.jpg')}
|
||||
>
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black rounded">
|
||||
<div
|
||||
className="iup-video-thumb rounded border-0"
|
||||
style={{
|
||||
backgroundImage: `url("${getThumbnail(
|
||||
'thumbnail_' + i + '.jpg'
|
||||
)}")`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="card-img-overlay rounded border-0">
|
||||
<div className="card-title align-middle text-center text-white">
|
||||
{__('Set', 'infinite-uploads')}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
thumbnails.push(
|
||||
<Col key="fileupload" className="mb-2">
|
||||
<Card
|
||||
className="h-100 p-0 border-4 border-secondary"
|
||||
style={{borderStyle: 'dashed'}}
|
||||
disabled={loading || uploading}
|
||||
role="button"
|
||||
onClick={() =>
|
||||
document.getElementById('upload-thumbnail').click()
|
||||
}
|
||||
>
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-light border-0 rounded">
|
||||
<div>
|
||||
{uploading ? (
|
||||
<div className="h-100 w-100 d-flex align-items-center justify-content-center">
|
||||
<Spinner
|
||||
animation="border"
|
||||
role="status"
|
||||
className="text-muted"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="dashicons dashicons-upload h-100 w-100 d-flex align-items-center justify-content-center text-muted h3"></span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Form.Control
|
||||
type="file"
|
||||
id="upload-thumbnail"
|
||||
className="d-none"
|
||||
accept="image/png, image/jpeg"
|
||||
disabled={loading || uploading}
|
||||
onChange={() =>
|
||||
uploadThumbnail(
|
||||
document.getElementById('upload-thumbnail')
|
||||
.files[0]
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
className="m-3 w-100 p-0 text-decoration-none"
|
||||
role="button"
|
||||
aria-label={__('Open video modal', 'infinite-uploads')}
|
||||
onClick={() => {
|
||||
if (selectVideo) {
|
||||
selectVideo(video);
|
||||
} else {
|
||||
handleShow();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
|
||||
<Modal
|
||||
show={show}
|
||||
onHide={handleClose}
|
||||
size="xl"
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
centered
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title id="contained-modal-title-vcenter">
|
||||
{__('Edit Video:', 'infinite-uploads')}{' '}
|
||||
{video.title}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Container fluid className="pb-3">
|
||||
<Row
|
||||
className="justify-content-center mb-4 mt-3"
|
||||
xs={1}
|
||||
lg={2}
|
||||
>
|
||||
<Col>
|
||||
<Row className="mb-2">
|
||||
<Col>
|
||||
<div className="ratio ratio-16x9">
|
||||
{iframe}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-between text-muted text-center">
|
||||
<Col>
|
||||
<VideoDate video={video}/>
|
||||
</Col>
|
||||
<Col>
|
||||
<VideoLength video={video}/>
|
||||
</Col>
|
||||
<Col>
|
||||
<VideoViews video={video}/>
|
||||
</Col>
|
||||
<Col>
|
||||
<VideoSize video={video}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col>
|
||||
<Row className="mb-4">
|
||||
<Col>
|
||||
<label htmlFor="video-title">
|
||||
{__(
|
||||
'Video Title',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</label>
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
id="video-title"
|
||||
placeholder={__(
|
||||
'Title',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
aria-label={__(
|
||||
'Title',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
value={title}
|
||||
onChange={(e) =>
|
||||
setTitle(e.target.value)
|
||||
}
|
||||
disabled={
|
||||
loading || uploading
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="text-white"
|
||||
disabled={
|
||||
loading || uploading
|
||||
}
|
||||
onClick={updateVideo}
|
||||
>
|
||||
{__(
|
||||
'Update',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="mb-4">
|
||||
<Col className="col-4">
|
||||
<h6>
|
||||
{__(
|
||||
'Current Thumbnail',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</h6>
|
||||
<Card className="bg-dark text-white w-100 p-0 mb-2">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black rounded border-0">
|
||||
<div
|
||||
className="iup-video-thumb rounded border-0"
|
||||
style={{
|
||||
backgroundImage: `url("${getThumbnail(
|
||||
video.thumbnailFileName
|
||||
)}")`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col className="col-8">
|
||||
<p>
|
||||
{__(
|
||||
'Choose a new thumbnail to be displayed in the video player:',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
<Row className="justify-content-start d-flex row-cols-2 row-cols-md-3">
|
||||
{thumbnails}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="justify-content-end mb-3">
|
||||
<Col className="justify-content-end d-flex">
|
||||
<DeleteModal
|
||||
video={video}
|
||||
setVideos={setVideos}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Tabs defaultActiveKey="shortcode" className="mb-4">
|
||||
<Tab
|
||||
eventKey="shortcode"
|
||||
title={
|
||||
<div className="d-inline-flex align-start">
|
||||
<span className="dashicons dashicons-shortcode me-1"></span>
|
||||
{__(
|
||||
'Embed Code',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Row className="justify-content-center mt-2">
|
||||
<Col>
|
||||
<Row>
|
||||
<Col>
|
||||
<p>
|
||||
{__(
|
||||
'Copy and paste this code into your post, page, or widget to embed the video. If using Gutenberg editor use our block.',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-1">
|
||||
<Col>
|
||||
<Form>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Autoplay',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={autoPlay}
|
||||
onChange={(e) =>
|
||||
setAutoPlay(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Loop',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={loop}
|
||||
onChange={(e) =>
|
||||
setLoop(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Muted',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={muted}
|
||||
onChange={(e) =>
|
||||
setMuted(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Preload',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={preload}
|
||||
onChange={(e) =>
|
||||
setPreload(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
style={{ display: 'none' }} // Hides the preload checkbox
|
||||
/>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<Form.Control
|
||||
type="text"
|
||||
aria-label="Embed Code"
|
||||
readOnly
|
||||
value={`[infinite-uploads-vid id="${video.guid}" ${embedParams}]`}
|
||||
onClick={(e) => {
|
||||
e.target.select();
|
||||
document.execCommand(
|
||||
'copy'
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
eventKey="stats"
|
||||
disabled
|
||||
title={
|
||||
<>
|
||||
<span className="dashicons dashicons-chart-area me-1"></span>
|
||||
{__('Stats', 'infinite-uploads')}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Row className="justify-content-center">
|
||||
<Col>
|
||||
<Row>
|
||||
<Col>
|
||||
<h5>
|
||||
{__(
|
||||
'Statistics',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</h5>
|
||||
<p>
|
||||
{__(
|
||||
'View the statistics for this video.',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>Chart here</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
eventKey="captions"
|
||||
disabled
|
||||
title={
|
||||
<>
|
||||
<span className="dashicons dashicons-format-status me-1"></span>
|
||||
{__('Captions', 'infinite-uploads')}
|
||||
</>
|
||||
}
|
||||
></Tab>
|
||||
|
||||
<Tab
|
||||
eventKey="chapters"
|
||||
disabled
|
||||
title={
|
||||
<>
|
||||
<span className="dashicons dashicons-text me-1"></span>
|
||||
{__('Chapters', 'infinite-uploads')}
|
||||
</>
|
||||
}
|
||||
></Tab>
|
||||
</Tabs>
|
||||
</Container>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import {render} from '@wordpress/element';
|
||||
import {Component} from '@wordpress/element';
|
||||
import Library from './components/Library';
|
||||
import Settings from "./components/Settings";
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import '../../assets/css/admin.css';
|
||||
|
||||
class InfiniteUploadsLibrary extends Component {
|
||||
render() {
|
||||
return <Library/>;
|
||||
}
|
||||
}
|
||||
|
||||
class InfiniteUploadsSettings extends Component {
|
||||
render() {
|
||||
return <Settings/>;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function (event) {
|
||||
const library = document.getElementById("iup-videos-page")
|
||||
if (library) {
|
||||
render(<InfiniteUploadsLibrary/>, library);
|
||||
}
|
||||
const settings = document.getElementById("iup-video-settings-page")
|
||||
if (settings) {
|
||||
render(<InfiniteUploadsSettings/>, settings);
|
||||
}
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "infinite-uploads/video",
|
||||
"version": "0.1.0",
|
||||
"title": "Infinite Uploads Video",
|
||||
"category": "media",
|
||||
"icon": "format-video",
|
||||
"description": "Upload & Embed a video via Infinite Uploads Video Cloud.",
|
||||
"attributes": {
|
||||
"video_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoplay": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"loop": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"muted": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"preload": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"html": false,
|
||||
"anchor": true,
|
||||
"align": true,
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true
|
||||
}
|
||||
},
|
||||
"textdomain": "infinite-uploads",
|
||||
"editorScript": "file:../../../build/block.js",
|
||||
"editorStyle": "file:../../../build/block.css",
|
||||
"style": "file:../../../build/style-block.css"
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import {__, _x} from '@wordpress/i18n';
|
||||
import {Button, Modal} from '@wordpress/components';
|
||||
import {useState, useEffect} from '@wordpress/element';
|
||||
import Library from '../../../admin/components/Library';
|
||||
import {InfiniteUploadsIcon} from '../Images';
|
||||
import './styles.scss';
|
||||
|
||||
export default function LibraryModal({selectVideo, ...props}) {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const openModal = () => setOpen(true);
|
||||
const closeModal = () => setOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="primary" onClick={openModal}>
|
||||
{__('Select from Library', 'infinite-uploads')}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<Modal
|
||||
{...props}
|
||||
isDismissible={true}
|
||||
onRequestClose={closeModal}
|
||||
icon={InfiniteUploadsIcon(false)}
|
||||
style={{width: '98%'}}
|
||||
title={__('Cloud Video Library', 'infinite-uploads')}
|
||||
className="iup-block-library-model"
|
||||
>
|
||||
<p>
|
||||
{__(
|
||||
'Select a video from your library to insert into the editor.',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
|
||||
<Library selectVideo={selectVideo}/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//scope bootstrap styles to library modal only
|
||||
.iup-block-library-model {
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
|
||||
.components-modal__header-heading {
|
||||
margin-bottom: 0;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.bg-black {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: #6c757d !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.rounded-top {
|
||||
border-top-left-radius: 0.375rem !important;
|
||||
border-top-right-radius: 0.375rem !important;
|
||||
}
|
||||
|
||||
.btn .dashicons {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-pill {
|
||||
border-radius: 50rem !important;
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import {__, _x} from '@wordpress/i18n';
|
||||
import {ToggleControl, SelectControl} from '@wordpress/components';
|
||||
import {useMemo, useCallback, Platform} from '@wordpress/element';
|
||||
|
||||
const VideoSettings = ({setAttributes, attributes}) => {
|
||||
const {autoplay, loop, muted, preload} = attributes;
|
||||
|
||||
const autoPlayHelpText = __(
|
||||
'Autoplay may cause usability issues for some users.'
|
||||
);
|
||||
const getAutoplayHelp = Platform.select({
|
||||
web: useCallback((checked) => {
|
||||
return checked ? autoPlayHelpText : null;
|
||||
}, []),
|
||||
native: autoPlayHelpText,
|
||||
});
|
||||
|
||||
const toggleFactory = useMemo(() => {
|
||||
const toggleAttribute = (attribute) => {
|
||||
return (newValue) => {
|
||||
setAttributes({[attribute]: newValue});
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
autoplay: toggleAttribute('autoplay'),
|
||||
loop: toggleAttribute('loop'),
|
||||
muted: toggleAttribute('muted'),
|
||||
preload: toggleAttribute('preload'),
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToggleControl
|
||||
label={__('Autoplay')}
|
||||
onChange={toggleFactory.autoplay}
|
||||
checked={autoplay}
|
||||
help={getAutoplayHelp}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Loop')}
|
||||
onChange={toggleFactory.loop}
|
||||
checked={loop}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Muted')}
|
||||
onChange={toggleFactory.muted}
|
||||
checked={muted}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Preload')}
|
||||
onChange={toggleFactory.preload}
|
||||
checked={preload}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoSettings;
|