deleted plugin Infinite Uploads version 2.0.8

This commit is contained in:
KawaiiPunk 2025-05-02 12:05:06 +00:00 committed by Gitium
parent 8fefb19ab4
commit 37e74c1bea
1179 changed files with 0 additions and 99739 deletions

View File

@ -1 +0,0 @@
<?php return array('dependencies' => array('react-jsx-runtime'), 'version' => '4dd50626eed3ac988f73');

View File

@ -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})();

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-element', 'wp-i18n'), 'version' => '83c76c2a8f08d59cdbc2');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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"
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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 */

File diff suppressed because one or more lines are too long

View File

@ -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 */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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);
}
};
});

View File

@ -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">&times;</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' );
}
}

View File

@ -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' );
}
}

View File

@ -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;
}
}

View File

@ -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&#8217;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();
}
}

View File

@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -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">&times;</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' );
}
}

View File

@ -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' );

View File

@ -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;
}
}

View File

@ -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; ?>">&nbsp;</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>

View File

@ -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; ?>">&nbsp;</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'] ) ); ?> &dash; <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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">&times;</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>

View File

@ -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">&times;</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>

View File

@ -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">&times;</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>

View File

@ -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">&times;</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>

View File

@ -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">&times;</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>

View File

@ -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">&times;</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>

View File

@ -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>

View File

@ -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>

View File

@ -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; ?>">&nbsp;</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'] ) ); ?> &dash; <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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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>
</>
);
}

View File

@ -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;

View File

@ -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>
</>
);
}

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
);
}

View File

@ -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;

View File

@ -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>
</>
);
}

View File

@ -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);
}
});

View File

@ -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"
}

View File

@ -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>
)}
</>
);
}

View File

@ -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;
}
}

View File

@ -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;

Some files were not shown because too many files have changed in this diff Show More